Optique 0.3.0: 종속 옵션과 유연한 구성

洪 民憙 (Hong Minhee) @hongminhee@hackers.pub

복잡한 CLI 애플리케이션 구축을 더 간단하게 만드는 여러 개선 사항이 포함된 Optique 0.3.0을 출시합니다. 이번 릴리스는 커뮤니티의 피드백, 특히 Fedify 프로젝트의 Cliffy에서 Optique로의 마이그레이션에서 받은 의견을 바탕으로 파서 유연성 확장과 도움말 시스템 개선에 중점을 두었습니다. 이 과정에서 귀중한 통찰력을 제공해 주신 @z9mb1@hackers.pub에게 특별히 감사드립니다.

새로운 기능

  • 종속 옵션 패턴을 위한 새로운 flag() 파서를 통한 필수 불리언 플래그
  • 조건부 CLI 구조를 지원하는 유니온 타입을 포함한 withDefault()유연한 타입 기본값
  • 이제 최대 10개의 파서를 지원하는 확장된 or() 용량 (이전에는 5개)
  • object() 뿐만 아니라 모든 객체 생성 파서와 함께 작동하는 향상된 merge() 결합기
  • 새로운 longestMatch() 결합기를 사용한 컨텍스트 인식 도움말
  • @optique/core@optique/run 모두에서 버전 표시 지원
  • 일관된 터미널 포맷팅을 위한 구조화된 출력 함수

flag()를 사용한 필수 불리언 플래그

새로운 flag() 파서는 명시적으로 제공되어야 하는 불리언 플래그를 생성합니다. option()이 없을 때 기본값이 false인 반면, flag()는 존재하지 않으면 파싱에 완전히 실패합니다. 이러한 미묘한 차이로 종속 옵션에 대한 더 깔끔한 패턴이 가능해집니다.

모드가 명시적으로 활성화된 경우에만 특정 옵션이 의미가 있는 시나리오를 고려해보세요:

import { flag, object, option, withDefault } from "@optique/core/parser";
import { integer } from "@optique/core/valueparser";

// --advanced 플래그 없이는 이러한 옵션을 사용할 수 없습니다
const parser = withDefault(
  object({
    advanced: flag("--advanced"),
    maxThreads: option("--threads", integer()),
    cacheSize: option("--cache-size", integer())
  }),
  { advanced: false as const }
);

// 사용법:
// myapp                    → { advanced: false }
// myapp --advanced         → 오류: --threads와 --cache-size가 필요함
// myapp --advanced --threads 4 --cache-size 100 → 성공

이 패턴은 확인 플래그(--yes-i-am-sure)나 CLI의 동작을 근본적으로 변경하는 모드 스위치에 특히 유용합니다.

withDefault()에서의 유니온 타입

이전에는 withDefault()가 기본값이 파서의 타입과 정확히 일치하도록 요구했습니다. 이제는 다른 타입을 지원하여 조건부 CLI 구조를 가능하게 하는 유니온 타입을 생성합니다:

const conditionalParser = withDefault(
  object({
    server: flag("-s", "--server"),
    port: option("-p", "--port", integer()),
    host: option("-h", "--host", string())
  }),
  { server: false as const }
);

// 결과 타입은 이제 유니온입니다:
// | { server: false }
// | { server: true, port: number, host: string }

이 변경으로 복잡한 or() 체인을 사용하지 않고도 다른 플래그가 다른 옵션 세트를 활성화하는 CLI를 훨씬 쉽게 구축할 수 있습니다.

더 유연해진 merge() 결합기

merge() 결합기는 이제 객체와 유사한 값을 생성하는 모든 파서를 허용합니다. 이전에는 object() 파서로 제한되었지만, 이제는 withDefault(), map() 및 기타 변환 파서와 함께 작동합니다:

const transformedConfig = map(
  object({
    host: option("--host", string()),
    port: option("--port", integer())
  }),
  ({ host, port }) => ({ endpoint: `${host}:${port}` })
);

const conditionalFeatures = withDefault(
  object({
    experimental: flag("--experimental"),
    debugLevel: option("--debug-level", integer())
  }),
  { experimental: false as const }
);

// 이제 다양한 파서 타입을 병합할 수 있습니다
const appConfig = merge(
  transformedConfig,        // map() 결과
  conditionalFeatures,      // withDefault() 파서
  object({                  // 전통적인 object()
    verbose: option("-v", "--verbose")
  })
);

이 개선은 많은 파서가 궁극적으로 객체를 생성한다는 것을 인식하고, merge()object() 파서로만 인위적으로 제한하는 것이 구성 패턴을 제한한다는 점에서 비롯되었습니다.

longestMatch()를 통한 컨텍스트 인식 도움말

새로운 longestMatch() 결합기는 가장 많은 입력 토큰을 소비하는 파서를 선택합니다. 이를 통해 command --help가 전역 도움말이 아닌 해당 특정 명령에 대한 도움말을 표시하는 정교한 도움말 시스템이 가능해집니다:

const normalParser = object({
  help: constant(false),
  command: or(
    command("list", listOptions),
    command("add", addOptions)
  )
});

const contextualHelp = object({
  help: constant(true),
  commands: multiple(argument(string())),
  helpFlag: flag("--help")
});

const cli = longestMatch(normalParser, contextualHelp);

// myapp --help           → 전역 도움말 표시
// myapp list --help      → 'list' 명령에 대한 도움말 표시
// myapp add --help       → 'add' 명령에 대한 도움말 표시

@optique/core/facade@optique/run 모두의 run() 함수는 이제 이 패턴을 자동으로 사용하므로, 추가 구성 없이도 CLI가 컨텍스트 인식 도움말을 제공합니다.

버전 표시 지원

@optique/core/facade@optique/run 모두 이제 --version 플래그와 version 명령을 통한 버전 표시를 지원합니다. 자세한 내용은 runners 문서를 참조하세요:

// @optique/run - 간단한 API
run(parser, {
  version: "1.0.0",  // --version 플래그 추가
  help: "both"
});

// @optique/core/facade - 상세 제어
run(parser, "myapp", args, {
  version: {
    mode: "both",     // --version 플래그와 version 명령 모두
    value: "1.0.0",
    onShow: process.exit
  }
});

API는 도움말 구성과 동일한 패턴을 따라 일관성과 예측 가능성을 유지합니다.

구조화된 출력 함수

@optique/run의 새로운 출력 함수는 자동 기능 감지와 함께 일관된 터미널 포맷팅을 제공합니다. messages 문서에서 자세히 알아보세요:

import { print, printError, createPrinter } from "@optique/run";
import { message } from "@optique/core/message";

// 자동 포맷팅이 있는 표준 출력
print(message`Processing ${filename}...`);

// 선택적 종료가 있는 stderr로의 오류 출력
printError(message`File ${filename} not found`, { exitCode: 1 });

// 특정 요구 사항에 맞는 사용자 정의 프린터
const debugPrint = createPrinter({
  stream: "stderr",
  colors: true,
  maxWidth: 80
});

debugPrint(message`Debug: ${details}`);

이러한 함수는 터미널 기능을 자동으로 감지하고 적절한 포맷팅을 적용하여 다양한 환경에서 CLI 출력이 일관되게 유지됩니다.

주요 변경 사항

이전 버전과의 호환성을 유지하려고 노력했지만, 알아두어야 할 몇 가지 변경 사항이 있습니다:

  • @optique/runhelp 옵션은 더 이상 "none"을 허용하지 않습니다. 도움말을 비활성화하려면 단순히 옵션을 생략하세요.
  • getDocFragments()를 구현하는 사용자 정의 파서는 직접적인 상태 값 대신 DocState<TState>를 사용하도록 서명을 업데이트해야 합니다.
  • object() 파서는 이제 탐욕적 파싱을 사용하여 일치하는 모든 필드를 한 번에 소비하려고 시도합니다. 이는 대부분의 사용 사례에 영향을 미치지 않지만 복잡한 시나리오에서 파싱 순서가 변경될 수 있습니다.

0.3.0으로 업그레이드

Optique 0.3.0으로 업그레이드하려면 두 패키지를 모두 업데이트하세요:

# Deno (JSR)
deno add @optique/core@^0.3.0 @optique/run@^0.3.0

# npm
npm update @optique/core @optique/run

# pnpm
pnpm update @optique/core @optique/run

# Yarn
yarn upgrade @optique/core @optique/run

# Bun
bun update @optique/core @optique/run

코어 패키지만 사용하는 경우:

# Deno (JSR)
deno add @optique/core@^0.3.0

# npm
npm update @optique/core

향후 계획

이러한 개선 사항은 실제 사용 사례와 커뮤니티 피드백에서 비롯되었습니다. 특히 새로운 종속 옵션 패턴이 여러분의 사용 사례에 어떻게 작동하는지, 그리고 컨텍스트 인식 도움말 시스템이 여러분의 요구를 충족하는지에 대한 의견을 듣고 싶습니다.

항상 그렇듯이 optique.dev에서 전체 문서를 찾을 수 있으며, GitHub에서 이슈나 제안을 제출할 수 있습니다.

3

No comments

If you have a fediverse account, you can comment on this article from your own instance. Search https://hackers.pub/ap/articles/0198f5c6-9253-7728-a7d5-5a7868e1f724 on your instance and reply to it.