summaryrefslogtreecommitdiffstats
path: root/src/cli.ts
blob: 388e8f4e788ffe7155a59559bdf76c6bfa854de6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#!/usr/bin/env node
/**
 * Minimal CLI: `cepis 01001000`.
 *
 * It exists for one concrete case — quickly confirming a local instance
 * answers before pointing code at it:
 *
 * ```sh
 * cepis --base-url http://localhost:8080 01001000
 * ```
 *
 * A single CEP goes through the unit lookup, which carries `geo`; several go
 * through the batch, which is one request instead of N and has no `geo`.
 *
 * It is not the library's main interface; for that, import the package.
 *
 * @module
 */

import { Client, DEFAULT_URL } from "./client.js";
import { CepisError } from "./errors.js";
import type { Address } from "./models.js";
import { VERSION } from "./version.js";

const USAGE = `cepis ${VERSION} — look CEPs up against the cep.is API.

usage: cepis [--base-url URL] [--timeout MS] <cep> [cep...]

  --base-url URL   API URL (default: ${DEFAULT_URL})
  --timeout MS     milliseconds allowed per attempt (default: 10000)
  -h, --help       show this message
`;

interface Options {
  baseUrl: string;
  timeoutMs: number;
  ceps: string[];
}

function parseArgs(argv: readonly string[]): Options | null {
  const options: Options = {
    baseUrl: DEFAULT_URL,
    timeoutMs: 10_000,
    ceps: [],
  };

  for (let i = 0; i < argv.length; i += 1) {
    const arg = argv[i] as string;
    if (arg === "-h" || arg === "--help") {
      return null;
    } else if (arg === "--base-url") {
      i += 1;
      const value = argv[i];
      if (value === undefined) {
        throw new Error("--base-url needs a value");
      }
      options.baseUrl = value;
    } else if (arg === "--timeout") {
      i += 1;
      const value = Number(argv[i]);
      if (!Number.isFinite(value) || value <= 0) {
        throw new Error("--timeout needs a positive number of milliseconds");
      }
      options.timeoutMs = value;
    } else if (arg.startsWith("-")) {
      throw new Error(`unknown option: ${arg}`);
    } else {
      options.ceps.push(arg);
    }
  }

  if (options.ceps.length === 0) {
    return null;
  }
  return options;
}

function serialise(address: Address | null): unknown {
  if (address === null) {
    return null;
  }
  return {
    cep: address.cep,
    formattedCep: address.formattedCep,
    logradouro: address.logradouro,
    complemento: address.complemento,
    unidade: address.unidade,
    bairro: address.bairro,
    localidade: address.localidade,
    uf: address.uf,
    estado: address.estado,
    regiao: address.regiao,
    ibge: address.ibge,
    gia: address.gia,
    ddd: address.ddd,
    siafi: address.siafi,
    geo: address.geo,
  };
}

/** Run the CLI. Resolves to the process exit code. */
export async function main(argv: readonly string[]): Promise<number> {
  let options: Options | null;
  try {
    options = parseArgs(argv);
  } catch (error) {
    process.stderr.write(`error: ${(error as Error).message}\n\n${USAGE}`);
    return 2;
  }

  if (options === null) {
    process.stdout.write(USAGE);
    return 0;
  }

  const client = new Client({
    baseUrl: options.baseUrl,
    timeoutMs: options.timeoutMs,
  });

  let addresses: (Address | null)[];
  try {
    addresses =
      options.ceps.length === 1
        ? [await client.lookup(options.ceps[0] as string)]
        : await client.lookupMany(options.ceps);
  } catch (error) {
    if (error instanceof CepisError) {
      process.stderr.write(`error: ${error.message}\n`);
      return 1;
    }
    throw error;
  }

  const output = addresses.map(serialise);
  process.stdout.write(`${JSON.stringify(output.length > 1 ? output : output[0], null, 2)}\n`);
  return 0;
}

process.exitCode = await main(process.argv.slice(2));