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
10 changes: 10 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
BasedOnStyle: LLVM
IndentWidth: 4
ContinuationIndentWidth: 4
ColumnLimit: 0
PointerAlignment: Left
ReferenceAlignment: Left
AlignConsecutiveAssignments: Consecutive
AlignConsecutiveDeclarations: Consecutive
...
172 changes: 172 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,69 @@ concurrency:

permissions:
contents: read
checks: write
pull-requests: write

jobs:
lint-cpp:
name: C++ Lint (clang-format + clang-tidy)
runs-on: ubuntu-22.04
env:
CARGO_TERM_COLOR: always
steps:
- name: Checkout (with submodules)
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0

- name: Install system dependencies
shell: bash
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
cmake \
ninja-build \
clang-format \
clang-tidy

- name: Set up Rust (stable)
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable

- name: Configure (CMake, compile commands)
shell: bash
run: |
set -euo pipefail
cmake -S . -B build -G Ninja -DSIDERUST_BUILD_DOCS=OFF -DCMAKE_EXPORT_COMPILE_COMMANDS=ON

- name: clang-format check
shell: bash
run: |
set -euo pipefail
mapfile -t files < <(git ls-files '*.hpp' '*.cpp')
if [ ${#files[@]} -eq 0 ]; then
echo "No C++ files found."
exit 0
fi
clang-format --dry-run --Werror "${files[@]}"

- name: clang-tidy check
shell: bash
run: |
set -euo pipefail
mapfile -t cpp_files < <(git ls-files '*.cpp')
if [ ${#cpp_files[@]} -eq 0 ]; then
echo "No C++ source files found."
exit 0
fi
for file in "${cpp_files[@]}"; do
echo "Running clang-tidy on ${file}"
clang-tidy -p build --warnings-as-errors='*' "${file}"
done

build-test-docs:
name: Build + Test + Docs
runs-on: ubuntu-22.04
Expand Down Expand Up @@ -90,3 +151,114 @@ jobs:
run: |
set -euo pipefail
cmake --build build --target docs

coverage:
name: Test & Coverage
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
runs-on: ubuntu-22.04
env:
CARGO_TERM_COLOR: always
steps:
- name: Checkout (with submodules)
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0

- name: Install system dependencies
shell: bash
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential \
cmake \
ninja-build \
pkg-config \
libssl-dev \
gcovr

- name: Set up Rust (stable)
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable

- name: Configure (CMake, coverage)
shell: bash
run: |
set -euo pipefail
cmake -S . -B build-coverage -G Ninja \
-DSIDERUST_BUILD_DOCS=OFF \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_CXX_FLAGS="--coverage" \
-DCMAKE_EXE_LINKER_FLAGS="--coverage"

- name: Build tests
shell: bash
run: |
set -euo pipefail
cmake --build build-coverage --target test_siderust

- name: Run tests
shell: bash
run: |
set -euo pipefail
ctest --test-dir build-coverage --output-on-failure -L siderust_cpp

- name: Coverage (Cobertura XML)
shell: bash
run: |
set -euo pipefail
gcovr \
--root . \
--exclude 'build-coverage/.*' \
--exclude 'siderust/.*' \
--exclude 'qtty-cpp/.*' \
--exclude 'tempoch-cpp/.*' \
--exclude 'tests/.*' \
--exclude 'examples/.*' \
--xml \
--output coverage.xml

- name: Coverage (HTML)
shell: bash
run: |
set -euo pipefail
mkdir -p coverage_html
gcovr \
--root . \
--exclude 'build-coverage/.*' \
--exclude 'siderust/.*' \
--exclude 'qtty-cpp/.*' \
--exclude 'tempoch-cpp/.*' \
--exclude 'tests/.*' \
--exclude 'examples/.*' \
--html-details \
--output coverage_html/index.html

- name: Build coverage summary (Markdown)
uses: irongut/CodeCoverageSummary@v1.3.0
with:
filename: coverage.xml
badge: true
format: markdown
output: file

- name: Publish to Job Summary
shell: bash
run: cat code-coverage-results.md >> "$GITHUB_STEP_SUMMARY"

- name: Post coverage as PR comment
if: github.event_name == 'pull_request'
uses: mshick/add-pr-comment@v2
with:
message-path: code-coverage-results.md
repo-token: ${{ secrets.GITHUB_TOKEN }}

- name: Upload coverage HTML
if: always()
uses: actions/upload-artifact@v4
with:
name: coverage-html
path: coverage_html
retention-days: 7
49 changes: 27 additions & 22 deletions examples/altitude_events_example.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/**
* @file altitude_events_example.cpp
* @brief Altitude windows, crossings, and culminations for Sun, Moon, and stars.
* @brief Altitude windows, crossings, and culminations for Sun, Moon, and
* stars.
*
* Usage:
* cmake --build build-make --target altitude_events_example
Expand All @@ -24,14 +25,15 @@ static const char* culmination_kind_name(CulminationKind k) {
}

static void print_utc(const UTC& utc) {
std::printf("%04d-%02u-%02u %02u:%02u:%02u",
utc.year, utc.month, utc.day,
std::printf("%04d-%02u-%02u %02u:%02u:%02u", utc.year, utc.month, utc.day,
utc.hour, utc.minute, utc.second);
}

static void print_periods(const char* title, const std::vector<Period>& periods, std::size_t max_items = 4) {
static void print_periods(const char* title, const std::vector<Period>& periods,
std::size_t max_items = 4) {
std::printf("%s: %zu period(s)\n", title, periods.size());
const std::size_t n = (periods.size() < max_items) ? periods.size() : max_items;
const std::size_t n =
(periods.size() < max_items) ? periods.size() : max_items;
for (std::size_t i = 0; i < n; ++i) {
const auto s = periods[i].start().to_utc();
const auto e = periods[i].end().to_utc();
Expand All @@ -46,7 +48,9 @@ static void print_periods(const char* title, const std::vector<Period>& periods,
}
}

static void print_crossings(const char* title, const std::vector<CrossingEvent>& events, std::size_t max_items = 6) {
static void print_crossings(const char* title,
const std::vector<CrossingEvent>& events,
std::size_t max_items = 6) {
std::printf("%s: %zu event(s)\n", title, events.size());
const std::size_t n = (events.size() < max_items) ? events.size() : max_items;
for (std::size_t i = 0; i < n; ++i) {
Expand All @@ -60,15 +64,16 @@ static void print_crossings(const char* title, const std::vector<CrossingEvent>&
}
}

static void print_culminations(const char* title, const std::vector<CulminationEvent>& events, std::size_t max_items = 6) {
static void print_culminations(const char* title,
const std::vector<CulminationEvent>& events,
std::size_t max_items = 6) {
std::printf("%s: %zu event(s)\n", title, events.size());
const std::size_t n = (events.size() < max_items) ? events.size() : max_items;
for (std::size_t i = 0; i < n; ++i) {
const auto t = events[i].time.to_utc();
std::printf(" %zu) ", i + 1);
print_utc(t);
std::printf(" alt=%.3f deg kind=%s\n",
events[i].altitude.value(),
std::printf(" alt=%.3f deg kind=%s\n", events[i].altitude.value(),
culmination_kind_name(events[i].kind));
}
if (events.size() > n) {
Expand All @@ -79,9 +84,9 @@ static void print_culminations(const char* title, const std::vector<CulminationE
int main() {
std::printf("=== Altitude Events Example ===\n\n");

const auto obs = MAUNA_KEA;
const auto start = MJD::from_utc({2026, 7, 15, 0, 0, 0});
const auto end = start + 2.0;
const auto obs = MAUNA_KEA;
const auto start = MJD::from_utc({2026, 7, 15, 0, 0, 0});
const auto end = start + 2.0;
const Period window(start, end);

std::printf("Observer: Mauna Kea (lon=%.4f lat=%.4f h=%.0f m)\n",
Expand All @@ -94,7 +99,7 @@ int main() {
// Sun examples.
const auto sun_night = sun::below_threshold(obs, window, -18.0_deg, opts);
const auto sun_cross = sun::crossings(obs, window, -0.833_deg, opts);
const auto sun_culm = sun::culminations(obs, window, opts);
const auto sun_culm = sun::culminations(obs, window, opts);
print_periods("Sun below -18 deg (astronomical night)", sun_night);
print_crossings("Sun crossings at -0.833 deg", sun_cross);
print_culminations("Sun culminations", sun_culm);
Expand All @@ -103,16 +108,18 @@ int main() {
// Moon examples.
const auto moon_above = moon::above_threshold(obs, window, 20.0_deg, opts);
const auto moon_cross = moon::crossings(obs, window, 0.0_deg, opts);
const auto moon_culm = moon::culminations(obs, window, opts);
const auto moon_culm = moon::culminations(obs, window, opts);
print_periods("Moon above +20 deg", moon_above);
print_crossings("Moon horizon crossings", moon_cross);
print_culminations("Moon culminations", moon_culm);
std::printf("\n");

// Star examples.
const auto& vega = VEGA;
const auto vega_above = star_altitude::above_threshold(vega, obs, window, 25.0_deg, opts);
const auto vega_cross = star_altitude::crossings(vega, obs, window, 0.0_deg, opts);
const auto vega_above =
star_altitude::above_threshold(vega, obs, window, 25.0_deg, opts);
const auto vega_cross =
star_altitude::crossings(vega, obs, window, 0.0_deg, opts);
const auto vega_culm = star_altitude::culminations(vega, obs, window, opts);
print_periods("VEGA above +25 deg", vega_above);
print_crossings("VEGA horizon crossings", vega_cross);
Expand All @@ -121,12 +128,10 @@ int main() {

// Fixed ICRS direction examples.
const spherical::direction::ICRS dir_icrs(279.23473_deg, 38.78369_deg);
const auto dir_above = icrs_altitude::above_threshold(
dir_icrs, obs, window, 30.0_deg, opts
);
const auto dir_below = icrs_altitude::below_threshold(
dir_icrs, obs, window, 0.0_deg, opts
);
const auto dir_above =
icrs_altitude::above_threshold(dir_icrs, obs, window, 30.0_deg, opts);
const auto dir_below =
icrs_altitude::below_threshold(dir_icrs, obs, window, 0.0_deg, opts);
print_periods("Fixed ICRS direction above +30 deg", dir_above);
print_periods("Fixed ICRS direction below horizon", dir_below);

Expand Down
18 changes: 8 additions & 10 deletions examples/coordinate_systems_example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ int main() {
std::printf("=== Coordinate Systems Example ===\n\n");

auto obs = ROQUE_DE_LOS_MUCHACHOS;
auto jd = JulianDate::from_utc({2026, 7, 15, 22, 0, 0});
auto jd = JulianDate::from_utc({2026, 7, 15, 22, 0, 0});

std::printf("Observer (Geodetic): lon=%.4f deg lat=%.4f deg h=%.1f m\n",
obs.lon.value(), obs.lat.value(), obs.height.value());

auto ecef_m = obs.to_cartesian<qtty::Meter>();
auto ecef_m = obs.to_cartesian<qtty::Meter>();
auto ecef_km = obs.to_cartesian<qtty::Kilometer>();
std::printf("Observer (ECEF): x=%.2f m y=%.2f m z=%.2f m\n",
ecef_m.x().value(), ecef_m.y().value(), ecef_m.z().value());
Expand All @@ -34,11 +34,11 @@ int main() {
// Vega J2000 ICRS direction.
spherical::direction::ICRS vega_icrs(279.23473, 38.78369);

auto vega_ecl = vega_icrs.to<EclipticMeanJ2000>(jd);
auto vega_ecl = vega_icrs.to<EclipticMeanJ2000>(jd);
auto vega_eq_mod = vega_icrs.to<EquatorialMeanOfDate>(jd);
auto vega_eq_tod = vega_icrs.to<EquatorialTrueOfDate>(jd);
auto vega_hor = vega_icrs.to_horizontal(jd, obs);
auto vega_back = vega_ecl.to<ICRS>(jd);
auto vega_hor = vega_icrs.to_horizontal(jd, obs);
auto vega_back = vega_ecl.to<ICRS>(jd);

std::printf("Vega ICRS: RA=%.6f Dec=%.6f\n",
vega_icrs.ra().value(), vega_icrs.dec().value());
Expand All @@ -54,8 +54,7 @@ int main() {
vega_back.ra().value(), vega_back.dec().value());

spherical::position::ICRS<qtty::AstronomicalUnit> target_sph_au(
120.0_deg, -25.0_deg, 2.0_au
);
120.0_deg, -25.0_deg, 2.0_au);
auto target_dir = target_sph_au.direction();
std::printf("Spherical ICRS position: RA=%.2f Dec=%.2f dist=%.3f AU\n",
target_sph_au.ra().value(),
Expand All @@ -64,12 +63,11 @@ int main() {
std::printf("Direction extracted from spherical position: RA=%.2f Dec=%.2f\n\n",
target_dir.ra().value(), target_dir.dec().value());

cartesian::position::ICRS<qtty::Meter> target_cart_m(1.5e11, -3.0e10, 2.0e10);
cartesian::position::ICRS<qtty::Meter> target_cart_m(1.5e11, -3.0e10, 2.0e10);
cartesian::position::ICRS<qtty::AstronomicalUnit> target_cart_au(
target_cart_m.x().to<qtty::AstronomicalUnit>(),
target_cart_m.y().to<qtty::AstronomicalUnit>(),
target_cart_m.z().to<qtty::AstronomicalUnit>()
);
target_cart_m.z().to<qtty::AstronomicalUnit>());

std::printf("Cartesian ICRS position: x=%.3e m y=%.3e m z=%.3e m\n",
target_cart_m.x().value(),
Expand Down
Loading