Noora Documentation

repository·main·Indexed 18 days ago

https://github.com/tuist/noora

A Swift package for building command-line interfaces with interactive terminal UI components. Noora provides a central entry point for rendering prompts (yes/no, text, multiple choice), alerts (success, warning, error, info), progress indicators (bars, steps, collapsible steps), and structured data tables. It supports automatic mode switching between interactive and non-interactive terminals, custom theming, and platform support for Windows and Musl targets.

Tokens
29.7K
Snippets
87
Records
124
Agent score
63%

What's inside Noora

  1. Use TerminalText for semantic terminal styling

    main

    The TerminalText API allows you to apply semantic formatting to terminal output. Instead of hardcoding colors, you use semantic components that adapt to the user's terminal capabilities and theme.

    Note: It is recommended to use this API sparingly. Prefer using built-in TerminalText components for standard output whenever possible, and only use custom TerminalText strings when the specific semantics of your text are not covered by existing components.

  2. Configure signal handling behavior

    main
    Version 0.53.0 introduced configurable signal handling behavior. Additionally, version 0.57.0 improved terminal stability by ensuring terminal raw mode is restored on SIGINT and SIGTERM signals.
  3. How Noora components work

    main
    Noora follows a pattern where a single central instance of Noora acts as the entry point for all terminal UI components. Instead of importing individual components, you instantiate Noora() and call its methods to render specific UI elements (like prompts, choices, or status indicators) to the terminal.
  4. Navigate and select rows in a Selectable Table

    main

    The Selectable Table component is interactive and supports the following keyboard controls:

    KeyAction
    / kMove selection up
    / jMove selection down
    Enter / SpaceSelect current row
    Page UpMove to previous page
    Page DownMove to next page
    HomeGo to first page
    EndGo to last page
    Esc / qCancel selection (throws error)
  5. Show a success alert with Noora

    main

    Use the .success() method on a Noora instance to display a success message to the user. You can provide a simple string message or a more complex .alert configuration that includes a list of recommended next steps.

    // Simple success message
    Noora().success("The project has been successfully initialized")
    
    // Success alert with next steps
    Noora().success(.alert("The project has been successfully initialized", nextSteps: [
      "Run \(.command("tuist registry setup")) to speed up package resolution.",
      "Cache your project targets as binaries with \(.command("tuist cache")).",
    ]))
  6. Use the Collapsible step component

    main

    The collapsibleStep component is used to represent long-running tasks that stream output. It displays a limited number of recent output lines while the task is running and automatically collapses the output upon completion. This prevents the user from losing context while keeping the interface clean. It supports both interactive and non-interactive modes.

    try await Noora().collapsibleStep(
        title: "Build",
        successMessage: "Build succeeded",
        errorMessage: "Build failed",
        visibleLines: 3
    ) { progress in
        try await xcodebuild() { progress($0) }
    }
  7. Use the Updating Table component

    main

    The Updating Table component renders tabular data and automatically refreshes whenever new data is emitted from an async source. It is designed for streaming datasets like status dashboards or build logs. Because the table re-renders when new data arrives, interactivity is required to manage the UI state.

    To use it, provide an initial TableData object and an AsyncStream (or any async sequence) that emits updated TableData instances.

    let columns = [
        TableColumn(title: "SSID", width: .auto, alignment: .left),
        TableColumn(title: "Signal", width: .auto, alignment: .right)
    ]
    
    let initial = TableData(
        columns: columns,
        rows: [
            ["Home", "-40 dBm"].map(TerminalText.init)
        ]
    )
    
    let updates = AsyncStream<TableData> { continuation in
        wifiScanner.onChange { networks in
            let rows = networks.map { [$0.ssid, "\($0.rssi) dBm"].map(TerminalText.init) }
            continuation.yield(TableData(columns: columns, rows: rows))
        }
    }
    
    await Noora().table(initial, updates: updates)
  8. Use Noora terminal UI components

    main

    Noora provides several categories of components for building terminal interfaces:

    • Prompts: Interactive user input (yes/no choices, text input, single choice selection).
    • Alerts: Status messages (success, warning, error notifications).
    • Progress: Visual progress indicators (progress bars, step indicators).
    • Text Styling: Consistent typography and formatting.

    To use them, import Noora and instantiate the Noora() object to access the component methods.

    import Noora
    
    Noora().yesOrNoChoicePrompt(
      title: "Authentication",
      question: "Would you like to authenticate?",
      defaultAnswer: true,
      description: "Authentication is required to use some CLI features."
    )
  9. Use Noora components in your CLI

    main

    To use Noora, first create an instance of Noora. All available UI components are accessed as methods on this instance. For example, you can use yesOrNoChoicePrompt to create a boolean selection prompt.

    import Noora
    
    Noora().yesOrNoChoicePrompt(
      title: "Authentication",
      question: "Would you like to authenticate?",
      defaultAnswer: true,
      description: "Authentication is required to use some CLI features."
    )
  10. Install Noora via Swift Package Manager

    main

    To use Noora in your Swift project, add it as a dependency in your Package.swift file using the following URL and version requirement:

    .package(url: "https://github.com/tuist/Noora", .upToNextMajor(from: "0.15.0"))
  11. Use the Progress step component

    main

    The progressStep component represents a step in a command's execution. It tracks the time taken to complete a task and can display different messages based on the outcome (success or failure). It supports both interactive modes (with a spinner and message updates) and non-interactive modes.

    let graph = try await Noora().progressStep(
        message: "Processing the graph",
        successMessage: "Project graph processed",
        errorMessage: "Failed to process the project graph"
    ) { updateMessage in
        // An example of an asynchronous task.
        let graph = try await loadGraph()
    
        // You can use updateMessage to update the progress step message.
        updateMessage("Analyzing the graph")
    
        // Another asynchronous task.
        try await analyzeGraph(graph)
    
        return graph
    }
  12. Use the Selectable Updating Table

    main

    The selectableTable method allows users to navigate and select a row even while the underlying data is continuously updating. This is useful when you need to pick an item from a list that changes frequently (e.g., a list of active network connections).

    When using selectableTable, you typically maintain a local reference to the latest data snapshot to ensure that when a selection index is returned, you can map it back to the correct row content.

    let columns = [
        TableColumn(title: "SSID", width: .auto, alignment: .left),
        TableColumn(title: "Signal", width: .auto, alignment: .right),
    ]
    
    var latest = TableData(columns: columns, rows: [["Home", "-40 dBm"].map(TerminalText.init)])
    let updates = AsyncStream<TableData> { continuation in
        Task.detached {
            while !Task.isCancelled {
                latest = makeSnapshot() // Build new TableData from your source
                continuation.yield(latest)
                try? await Task.sleep(for: .seconds(1))
            }
            continuation.finish()
        }
    }
    
    let selectedIndex = try await Noora().selectableTable(
        latest,
        updates: updates,
        pageSize: 8
    )
    
    let selectedRow = latest.rows[selectedIndex]
    print("Picked network: \(selectedRow[0].plain())")