Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Preserve upstream document bytes and original whitespace on every platform.
upstream/** -text -whitespace
92 changes: 92 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
mcpp:
name: mcpp (${{ matrix.os }}, ${{ matrix.toolchain }})
strategy:
fail-fast: false
matrix:
include:
- { os: ubuntu-24.04, toolchain: 'gcc@16.1.0' }
- { os: ubuntu-24.04, toolchain: 'llvm@23.1.3' }
- { os: ubuntu-24.04-arm, toolchain: 'gcc@16.1.0' }
- { os: macos-15, toolchain: 'llvm@23.1.3' }
- { os: windows-2025, toolchain: 'llvm@23.1.3' }
runs-on: ${{ matrix.os }}
timeout-minutes: 60
defaults:
run:
shell: bash
env:
XLINGS_NON_INTERACTIVE: 1
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Mwrap identity and bilingual README contract
run: python3 tools/check_mwrap_release.py

- name: Install Xlings (Linux/macOS)
if: runner.os != 'Windows'
run: |
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/refs/heads/main/tools/other/quick_install.sh | bash
echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH"

- name: Install Xlings (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
irm https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.ps1 | iex
"$env:USERPROFILE\.xlings\subos\current\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append

- name: Install project tools (.xlings.json)
run: |
for attempt in 1 2 3; do
xlings update > /dev/null 2>&1 || true
if xlings install; then break; fi
[ "$attempt" = 3 ] && exit 1
sleep 30
done
mcpp --version

- name: Select the toolchain
run: |
name="${{ matrix.toolchain }}"
mcpp toolchain install "${name%@*}" "${name#*@}"
mcpp toolchain default "$name"

# Until mcpp-index publishes compat.sheenbidi (mcpplibs/mcpp-index#607),
# the compat namespace of this checkout's manifests resolves from that
# PR's branch, as an unchanged namespace view of its descriptors. Once
# the published index has the descriptor, this step does nothing.
- name: Candidate compat index (only while compat.sheenbidi is unpublished)
run: python3 tools/candidate_index.py --ci

- name: Build the modules
run: mcpp build

- name: Tests (the API, every macro and enumerator mapping against the header, header mixing)
run: mcpp test

- name: Export audit (the generated sources are fresh; every declaration and macro of the public headers)
run: python3 tools/check_exports.py

- name: Unicode's conformance tests (BidiTest.txt, BidiCharacterTest.txt) on programs written against the module
run: python3 tools/run_conformance.py
49 changes: 49 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Release
on:
push:
tags: ["mwrap-*"]
permissions:
contents: read
jobs:
publish:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
actions: read
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Check tag, upstream version and bilingual entry pages
run: python3 tools/check_mwrap_release.py --tag "$GITHUB_REF_NAME"
- name: Require successful source CI at this exact commit
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
result=$(gh run list --repo "$GITHUB_REPOSITORY" --workflow ci.yml --commit "$GITHUB_SHA" --limit 1 --json status,conclusion --jq '.[0] | .status == "completed" and .conclusion == "success"')
test "$result" = true || { echo 'No successful source CI at the tag commit'; exit 1; }
- name: Build immutable source asset and publish
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
version="${GITHUB_REF_NAME#mwrap-}"
repo="${GITHUB_REPOSITORY#*/}"
asset="${repo}-${version}.tar.gz"
git archive --format=tar.gz --prefix="${repo}-${version}/" "$GITHUB_SHA" > "$RUNNER_TEMP/$asset"
(cd "$RUNNER_TEMP" && sha256sum "$asset" > "$asset.sha256")
if gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
echo 'Release already exists; refusing to change immutable assets'; exit 1
fi
extra=()
if [[ "$version" == *-rc* ]]; then extra+=(--prerelease); fi
gh release create "$GITHUB_REF_NAME" "$RUNNER_TEMP/$asset" "$RUNNER_TEMP/$asset.sha256" \
--repo "$GITHUB_REPOSITORY" --verify-tag --title "$repo $version" \
--notes "Module wrapper for upstream $version. New mwrap package identities become installable after mcpp-index publication; see the bilingual README for the tested support scope." "${extra[@]}"
33 changes: 33 additions & 0 deletions .github/workflows/templates.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Actual project templates
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: templates-${{ github.ref }}
cancel-in-progress: true
env:
MCPP_VERSION: "2026.10.26.1"
jobs:
templates:
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: Install the pinned mcpp
shell: bash
run: |
archive="mcpp-${MCPP_VERSION}-linux-x86_64.tar.gz"
curl -L -fsS -o "$archive" "https://github.com/mcpp-community/mcpp/releases/download/v${MCPP_VERSION}/$archive"
tar -xzf "$archive"
echo "$PWD/mcpp-${MCPP_VERSION}-linux-x86_64/bin" >> "$GITHUB_PATH"
# The candidate index (tools/candidate_index.py) only while mcpp-index
# does not publish compat.sheenbidi.
- name: Actual scaffold, strict build and run of the default template
shell: bash
run: |
index=$(python3 tools/candidate_index.py --checkout-only)
python3 tools/test_template.py --toolchain gcc@16.1.0 ${index:+--index "$index"}
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
target
compile_commands.json
mcpp.lock
.mcpp/
.xlings/
doctor.log
__pycache__/
5 changes: 5 additions & 0 deletions .xlings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"workspace": {
"xim:mcpp": "2026.10.26.1"
}
}
22 changes: 22 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
sheenbidi-m: C++ module wrapper
Copyright (c) 2026 mcpp-community contributors

The wrapper contributions are licensed under the Apache License, Version 2.0.
Upstream code, copied interfaces and dependencies retain their original
licenses and copyright notices. SheenBidi is distributed under the Apache
License, Version 2.0:

Copyright (C) 2014-2026 Muhammad Tayyab Akram

Its original README and license are retained in upstream/sheenbidi/. Its
character tables (Source/Data) are generated from the Unicode Character
Database 17.0.0, (c) Unicode, Inc., distributed under the Unicode License v3,
retained in upstream/unicode/license.txt. The library itself is built by
compat.sheenbidi from the same release; this wrapper links it, it does not
modify it.

The conformance runners in examples/sheenbidi read Unicode's BidiTest.txt and
BidiCharacterTest.txt (17.0.0) from the pinned release; the files are not
redistributed here.

Provenance and checksums are recorded in upstream/documents.json.
106 changes: 105 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,105 @@
# sheenbidi-m
# sheenbidi

> SheenBidi, the Unicode Bidirectional Algorithm, through C++23 modules.

**English** · [简体中文](README.zh.md) · [Upstream](https://github.com/Tehreer/SheenBidi) · [Releases](https://github.com/mcpplibs/sheenbidi-m/releases) · [mcpp](https://github.com/mcpp-community/mcpp) · [Index](https://github.com/mcpplibs/mcpp-index)

[![C++23](https://img.shields.io/badge/C%2B%2B-23-blue.svg)](https://en.cppreference.com/w/cpp/23)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
[![SheenBidi](https://img.shields.io/badge/SheenBidi-Apache--2.0-blue.svg)](upstream/sheenbidi/LICENSE)
[![mcpp-community](https://img.shields.io/badge/mcpp--community-mwrap-blue)](https://github.com/mcpp-community)
[![CI](https://github.com/mcpplibs/sheenbidi-m/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/mcpplibs/sheenbidi-m/actions/workflows/ci.yml)

SheenBidi turns text stored in logical order into the visual order a display
needs for Arabic, Hebrew and mixed-direction text (UAX #9, Unicode 17.0.0). It
is Apache-2.0 C with no dependencies, so it links statically without copyleft
terms. The upstream C API is unchanged: `SBAlgorithmCreate`,
`SBParagraphGetLevelsPtr`, `SBLineGetRunsPtr` and `SBMirrorLocatorMoveNext`
are the upstream globals of `<SheenBidi/SheenBidi.h>`. The library is
`compat.sheenbidi` 3.0.0. This package maps SheenBidi's macros and the
enumerators of its unnamed enums to constants and constexpr functions, each
checked against the header. Unicode's conformance tests pass on every CI run:
all 770,241 cases of BidiTest.txt and all 91,707 of BidiCharacterTest.txt, on
programs written against the module.

## Modules

- `import mcpplibs.mwrap.sheenbidi;`: the whole API of `<SheenBidi/SheenBidi.h>` (SBAlgorithm, SBParagraph, SBLine, SBRun, SBMirrorLocator, SBScriptLocator, SBCodepoint, SBCodepointSequence, SBAllocator, SBVersion), re-exporting the module below
- `import mcpplibs.mwrap.sheenbidi.macros;`: `SBBidiTypeR`, `SBScriptARAB`, `SBStringEncodingUTF8`, `SBLevelDefaultLTR`, `SBBidiTypeIsStrong` … in `mcpplibs::mwrap::sheenbidi::macros`

> More Modules API -> [Usage and API](docs/usage.md)

## Getting started

```sh
mcpp new bidi --template mcpplibs.mwrap.sheenbidi@3.0.0
cd bidi
mcpp run
```

The default template lays out UTF-8 lines as a renderer does: it resolves a
paragraph's embedding levels, then draws the line's runs in visual order with
mirrored brackets. Run `mcpp run -- "your text"` for your own lines.

```toml
[dependencies]
"mcpplibs.mwrap.sheenbidi" = "3.0.0"
```

```cpp
import std;
import mcpplibs.mwrap.sheenbidi;
using namespace mcpplibs::mwrap::sheenbidi::macros; // SBStringEncodingUTF8, SBLevelDefaultLTR …

int main() {
std::string_view text = "abc אבג (123)";
SBCodepointSequence seq{SBStringEncodingUTF8, text.data(), text.size()};
SBAlgorithmRef algorithm = SBAlgorithmCreate(&seq);
SBParagraphRef paragraph = SBAlgorithmCreateParagraph(algorithm, 0, text.size(), SBLevelDefaultLTR);
SBLineRef line = SBParagraphCreateLine(paragraph, 0, SBParagraphGetLength(paragraph));
const SBRun* runs = SBLineGetRunsPtr(line); // in visual order, offsets in UTF-8 bytes
for (SBUInteger i = 0; i < SBLineGetRunCount(line); ++i)
std::println("[{}, {}) level {}", runs[i].offset, runs[i].offset + runs[i].length, runs[i].level);
SBLineRelease(line);
SBParagraphRelease(paragraph);
SBAlgorithmRelease(algorithm);
}
```

## Macros and enumerators

Constants such as `SBLevelMax` keep the type their macro has (`int`).
Computations such as `SBBidiTypeIsStrong` and `SBCodepointIsValid` are
constexpr functions whose body is the macro itself, matching its value and
type for every argument type. SheenBidi's enumerators (`SBBidiTypeL`,
`SBScriptARAB` …) belong to unnamed enums, which a module cannot export, so
they are constants of the typedef the header gives them (`SBBidiType`,
`SBScript` …). `SB_PUBLIC`, `SB_DEPRECATED` and the `extern "C"` brackets stay
macros in `<mwrap/sheenbidi/macros.hpp>`, included before the import. Details:
[docs/usage.md](docs/usage.md).

## Examples

- [examples/sheenbidi](examples/sheenbidi): a layout tool (logical to visual order, levels, runs and
script runs of each paragraph) and Unicode's two conformance runners, written against the module.
[tools/run_conformance.py](tools/run_conformance.py) runs BidiTest.txt and BidiCharacterTest.txt on them.

## Build and testing

Linux (x86_64 GCC and LLVM, arm64 GCC), macOS and Windows CI build the
modules, then run the tests and the export audit
([tools/check_exports.py](tools/check_exports.py)). The audit checks that the
generated sources are fresh, that every declaration is exported and that
every macro is mapped or listed with its reason. CI also runs Unicode 17.0.0's
conformance tests from the pinned release, each file checked against the one
Unicode publishes: BidiTest.txt (770,241 cases) and BidiCharacterTest.txt
(91,707 cases, with mirroring). A separate job scaffolds the default template
as an installed package and runs it.

## License

[Apache-2.0](LICENSE) for the wrapper, by mcpp-community ([NOTICE](NOTICE)).
SheenBidi is Apache-2.0 too ([upstream/sheenbidi/LICENSE](upstream/sheenbidi/LICENSE)).
Its tables come from the Unicode Character Database
([Unicode License v3](upstream/unicode/license.txt)). Both notices are
deployed beside every program that depends on this package.
Loading
Loading