Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
fbad8d3
chore(v0.2.1): scaffold projects Signal.CANdy.Core, Signal.CANdy, Sig…
InitusNovus Aug 27, 2025
e294ebe
chore: rename solution to Signal.CANdy.sln
InitusNovus Aug 27, 2025
263e876
feat(Core): add initial Api.fs stub (version) and include in project
InitusNovus Aug 27, 2025
78563bd
feat(Core): expand Api.fs and project; add stubs (Ir.fs, Config.fs) g…
InitusNovus Aug 27, 2025
e462f61
feat(Core): introduce Errors DU and expand Api with typed stubs (pars…
InitusNovus Aug 27, 2025
184a596
feat(Core): add Dbc parser port (parseDbcFile) and wire into Api; add…
InitusNovus Aug 27, 2025
d49d4c8
feat(Core): implement code generation in Core (utils/message/registry…
InitusNovus Aug 27, 2025
8960a08
feat(CLI): wire CLI to Core Api.generateFromPaths; fix F# indentation…
InitusNovus Aug 27, 2025
6f89616
chore(pkg): add NuGet packaging metadata for Core and Facade projects
InitusNovus Aug 27, 2025
5b2ebe2
chore(packaging): add NuGet metadata, README.NuGet.md, and ignore art…
InitusNovus Aug 27, 2025
8595167
chore(ci,docs,version): add CI pack step, align Api.version to 0.2.1-…
InitusNovus Aug 27, 2025
bbb3ac9
docs(facade): XML docs + error-handling examples; build/tests green
InitusNovus Aug 27, 2025
1ad5391
chore(release): bump to 0.2.1 stable (Core/Facade), update Api.versio…
InitusNovus Aug 27, 2025
9e6a35d
fix(release): change package IDs to SignalCandy.Core and SignalCandy …
InitusNovus Aug 27, 2025
f04a18d
feat(docs,ci): add NuGet badges to README; add main-branch check to r…
InitusNovus Aug 27, 2025
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
13 changes: 13 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,19 @@ jobs:
run: |
./gen/build/test_runner test_fixed_suite_roundtrip

- name: Pack NuGet (Core/Facade)
run: |
dotnet pack -c Release src/Signal.CANdy.Core/Signal.CANdy.Core.fsproj -o artifacts
dotnet pack -c Release src/Signal.CANdy/Signal.CANdy.fsproj -o artifacts

- name: Upload NuGet packages
uses: actions/upload-artifact@v4
with:
name: nuget-packages
path: |
artifacts/*.nupkg
artifacts/*.snupkg

- name: Upload build artifacts (on failure)
if: failure()
uses: actions/upload-artifact@v4
Expand Down
39 changes: 38 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,61 @@ jobs:
with:
fetch-depth: 0

- name: Check if tag is on main branch
run: |
# Check if the tag points to a commit that's reachable from main
if ! git merge-base --is-ancestor ${{ github.sha }} origin/main 2>/dev/null; then
echo "Tag is not on main branch. Skipping release."
echo "SKIP_RELEASE=true" >> $GITHUB_ENV
else
echo "Tag is on main branch. Proceeding with release."
echo "SKIP_RELEASE=false" >> $GITHUB_ENV
fi

- name: Setup .NET
if: env.SKIP_RELEASE == 'false'
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'

- name: Build & Test (Release)
if: env.SKIP_RELEASE == 'false'
run: |
dotnet restore
dotnet build --configuration Release --nologo
dotnet test --configuration Release -v minimal --nologo

- name: Generate artifacts (optional)
if: env.SKIP_RELEASE == 'false'
run: |
dotnet run --project src/Generator -- --dbc examples/sample.dbc --out gen --config examples/config.yaml

- name: Create GitHub Release
- name: Pack NuGet (Core/Facade)
if: env.SKIP_RELEASE == 'false'
run: |
dotnet pack -c Release src/Signal.CANdy.Core/Signal.CANdy.Core.fsproj -o artifacts
dotnet pack -c Release src/Signal.CANdy/Signal.CANdy.fsproj -o artifacts

- name: Publish NuGet packages (stable only)
if: ${{ env.SKIP_RELEASE == 'false' && !contains(github.ref_name, '-') }}
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
run: |
dotnet nuget push artifacts/*.nupkg --source https://api.nuget.org/v3/index.json --api-key "$NUGET_API_KEY" --skip-duplicate

- name: Create GitHub Release (stable)
if: ${{ env.SKIP_RELEASE == 'false' && !contains(github.ref_name, '-') }}
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Create GitHub Prerelease (preview tags)
if: ${{ env.SKIP_RELEASE == 'false' && contains(github.ref_name, '-') }}
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
prerelease: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ bin/
obj/
tmp/
.fake
artifacts/
# External test data: ignore all contents by default; keep folder and docs only
external_test/*
!external_test/.gitkeep
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,28 @@
[![F#](https://img.shields.io/badge/F%23-language-blue.svg)](https://fsharp.org/)
[![Version](https://img.shields.io/github/v/release/InitusNovus/Signal-CANdy?include_prereleases)](https://github.com/InitusNovus/Signal-CANdy/releases)

[![NuGet SignalCandy](https://img.shields.io/nuget/v/SignalCandy.svg)](https://www.nuget.org/packages/SignalCandy/)
[![NuGet SignalCandy.Core](https://img.shields.io/nuget/v/SignalCandy.Core.svg)](https://www.nuget.org/packages/SignalCandy.Core/)

[![C99](https://img.shields.io/badge/C-99-blue.svg)](https://en.wikipedia.org/wiki/C99)
[![CAN DBC](https://img.shields.io/badge/protocol-CAN%20DBC-green.svg)](https://en.wikipedia.org/wiki/CAN_bus)

Languages: This README is in English. For Korean, see README.ko.md.

This project generates portable C99 parser modules (headers/sources) from a `.dbc` file using an F# code generator.

## 📦 NuGet Packages

- SignalCandy.Core — Core F# library (parsing, config, codegen)
- SignalCandy — C#-friendly facade over the Core

Install:

```pwsh
dotnet add package SignalCandy.Core --version 0.2.1
dotnet add package SignalCandy --version 0.2.1
```

## ⚡ Quick Start (5 minutes)

1) Check prerequisites
Expand Down
21 changes: 21 additions & 0 deletions DBC_Parser.sln → Signal.CANdy.sln
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{42F645A3
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Generator.Tests", "tests\Generator.Tests\Generator.Tests.fsproj", "{4A234D75-019C-479E-8E35-17F85A776EF4}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Signal.CANdy.Core", "src\Signal.CANdy.Core\Signal.CANdy.Core.fsproj", "{5FA4B1F9-EC6B-47FF-84BC-44B9B2225731}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Signal.CANdy", "src\Signal.CANdy\Signal.CANdy.fsproj", "{E7B40CFD-65C1-41BB-80DE-F77723D6BFCE}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Signal.CANdy.CLI", "src\Signal.CANdy.CLI\Signal.CANdy.CLI.fsproj", "{2029AC91-E903-43ED-A2E1-53D261747CFA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -28,9 +34,24 @@ Global
{4A234D75-019C-479E-8E35-17F85A776EF4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4A234D75-019C-479E-8E35-17F85A776EF4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4A234D75-019C-479E-8E35-17F85A776EF4}.Release|Any CPU.Build.0 = Release|Any CPU
{5FA4B1F9-EC6B-47FF-84BC-44B9B2225731}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5FA4B1F9-EC6B-47FF-84BC-44B9B2225731}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5FA4B1F9-EC6B-47FF-84BC-44B9B2225731}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5FA4B1F9-EC6B-47FF-84BC-44B9B2225731}.Release|Any CPU.Build.0 = Release|Any CPU
{E7B40CFD-65C1-41BB-80DE-F77723D6BFCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E7B40CFD-65C1-41BB-80DE-F77723D6BFCE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E7B40CFD-65C1-41BB-80DE-F77723D6BFCE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E7B40CFD-65C1-41BB-80DE-F77723D6BFCE}.Release|Any CPU.Build.0 = Release|Any CPU
{2029AC91-E903-43ED-A2E1-53D261747CFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2029AC91-E903-43ED-A2E1-53D261747CFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2029AC91-E903-43ED-A2E1-53D261747CFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2029AC91-E903-43ED-A2E1-53D261747CFA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{0F49A024-9812-48BA-8758-9560824B6BDD} = {3DE60BDA-DC11-4655-A9ED-5342DCD5577D}
{4A234D75-019C-479E-8E35-17F85A776EF4} = {42F645A3-CE8D-435E-A671-749CEB739F43}
{5FA4B1F9-EC6B-47FF-84BC-44B9B2225731} = {3DE60BDA-DC11-4655-A9ED-5342DCD5577D}
{E7B40CFD-65C1-41BB-80DE-F77723D6BFCE} = {3DE60BDA-DC11-4655-A9ED-5342DCD5577D}
{2029AC91-E903-43ED-A2E1-53D261747CFA} = {3DE60BDA-DC11-4655-A9ED-5342DCD5577D}
EndGlobalSection
EndGlobal
93 changes: 93 additions & 0 deletions src/Signal.CANdy.CLI/Program.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
open System
open Signal.CANdy.Core
open Signal.CANdy.Core.Errors

module Cli =
type Parsed = {
DbcPath: string option
OutDir: string option
ConfigPath: string option
ShowVersion: bool
ShowHelp: bool
Unknown: string list
}

let empty: Parsed =
{ DbcPath = None
OutDir = None
ConfigPath = None
ShowVersion = false
ShowHelp = false
Unknown = [] }

let usage () =
String.concat "\n" [
"Signal.CANdy.CLI — DBC → C code generator";
"";
"Usage:";
" signal-candy --dbc <file.dbc> --out <out_dir> [--config <config.yaml>]";
" signal-candy --version";
" signal-candy --help";
"";
"Options:";
" --dbc <path> Path to input DBC file (required)";
" --out <dir> Output directory for generated C files (required)";
" --config <path> Optional YAML config (phys_type, range_check, dispatch, etc.)";
" --version Print library version and exit";
" --help, -h Show this help and exit"
]

let parse (argv: string array): Parsed =
let rec loop i (st: Parsed): Parsed =
if i >= argv.Length then st
else
match argv.[i] with
| "--dbc" when i + 1 < argv.Length -> loop (i + 2) { st with DbcPath = Some argv.[i + 1] }
| "--out" when i + 1 < argv.Length -> loop (i + 2) { st with OutDir = Some argv.[i + 1] }
| "--config" when i + 1 < argv.Length -> loop (i + 2) { st with ConfigPath = Some argv.[i + 1] }
| "--version" -> loop (i + 1) { st with ShowVersion = true }
| "--help" | "-h" -> loop (i + 1) { st with ShowHelp = true }
| unk -> loop (i + 1) { st with Unknown = st.Unknown @ [ unk ] }
loop 0 empty

[<EntryPoint>]
let main argv: int =
let args = Cli.parse argv

if args.ShowHelp then
printfn "%s" (Cli.usage ())
0
elif args.ShowVersion then
printfn "%s" (Signal.CANdy.Core.Api.version ())
0
elif args.Unknown |> List.isEmpty |> not then
eprintfn "Unknown arguments: %s" (String.Join(" ", args.Unknown))
eprintfn "\n%s" (Cli.usage ())
2
else
match args.DbcPath, args.OutDir with
| Some dbc, Some outDir ->
try
let cfgOpt = args.ConfigPath
let t = Signal.CANdy.Core.Api.generateFromPaths dbc outDir cfgOpt
let res = t.GetAwaiter().GetResult()
match res with
| Ok files ->
printfn "Code generation successful."
printfn "Headers: %d, Sources: %d, Others: %d" (files.Headers.Length) (files.Sources.Length) (files.Others.Length)
0
| Error err ->
let msg =
match err with
| CodeGenError.TemplateError s -> sprintf "Template error: %s" s
| CodeGenError.IoError s -> sprintf "IO error: %s" s
| CodeGenError.Unknown s -> sprintf "Error: %s" s
eprintfn "%s" msg
1
with ex ->
eprintfn "Unhandled error: %s" ex.Message
1
| _ ->
eprintfn "Missing required arguments."
eprintfn "\n%s" (Cli.usage ())
2
16 changes: 16 additions & 0 deletions src/Signal.CANdy.CLI/Signal.CANdy.CLI.fsproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<Compile Include="Program.fs" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Signal.CANdy.Core\Signal.CANdy.Core.fsproj" />
</ItemGroup>

</Project>
59 changes: 59 additions & 0 deletions src/Signal.CANdy.Core/Api.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
module Signal.CANdy.Core.Api

open System.Threading.Tasks
open Signal.CANdy.Core.Ir
open Signal.CANdy.Core.Config
open Signal.CANdy.Core.Errors
open Signal.CANdy.Core.Dbc
open Signal.CANdy.Core.Codegen

/// Returns the current library snapshot version. Placeholder until full API is moved.
let version () = "0.2.1"

/// Parse a DBC file into IR. Stub for now.
let parseDbc (path: string) : Result<Ir, ParseError> =
Signal.CANdy.Core.Dbc.parseDbcFile path

/// Validate configuration object. Stub for now.
let validateConfig (config: Config) : Result<Config, ValidationError> =
Signal.CANdy.Core.Config.validate config

/// Generate code (sync) using IR and Config.
let generateCode (ir: Ir) (outputPath: string) (config: Config) : Result<GeneratedFiles, CodeGenError> =
Signal.CANdy.Core.Codegen.generate ir outputPath config

/// Generate code (async) using IR and Config.
let generateCodeAsync (ir: Ir) (outputPath: string) (config: Config) : Task<Result<GeneratedFiles, CodeGenError>> = task {
return generateCode ir outputPath config
}

/// Convenience: parse dbc, load config path (optional), and generate.
let generateFromPaths (dbcPath: string) (outputPath: string) (configPath: string option) : Task<Result<GeneratedFiles, CodeGenError>> = task {
// Load config (optional path -> YAML; otherwise sensible defaults)
let configResult : Result<Config, CodeGenError> =
match configPath with
| Some p ->
match Signal.CANdy.Core.Config.loadFromYaml p with
| Ok cfg -> Ok cfg
| Error ve -> Error (CodeGenError.Unknown (sprintf "Config error: %A" ve))
| None ->
Ok {
PhysType = "float"
PhysMode = "double"
RangeCheck = false
Dispatch = "binary_search"
CrcCounterCheck = false
MotorolaStartBit = "msb"
FilePrefix = "sc_"
}

match configResult with
| Error e -> return Error e
| Ok cfg ->
// Parse DBC
match Signal.CANdy.Core.Dbc.parseDbcFile dbcPath with
| Error pe -> return Error (CodeGenError.Unknown (sprintf "Parse error: %A" pe))
| Ok ir ->
// Delegate to codegen (currently stubbed)
return generateCode ir outputPath cfg
}
Loading