Skip to content
Open
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
58 changes: 58 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Go

on:
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:

jobs:
go-test:
name: Go Test - ${{ matrix.platform }}
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- runner: windows-latest
target: x86_64-pc-windows-gnu
platform: windows-amd64
lib_name: libbraillify_go.a
- runner: ubuntu-latest
target: x86_64-unknown-linux-gnu
platform: linux-amd64
lib_name: libbraillify_go.a
- runner: macos-14
target: aarch64-apple-darwin
platform: darwin-arm64
lib_name: libbraillify_go.a

steps:
- uses: actions/checkout@v4

- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
target: ${{ matrix.target }}

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: "1.21"

- name: Build native library
run: cargo build --release --target ${{ matrix.target }} -p braillify-go

- name: Copy native library
run: |
mkdir -p packages/go/libs/${{ matrix.platform }}
cp target/${{ matrix.target }}/release/${{ matrix.lib_name }} packages/go/libs/${{ matrix.platform }}/
shell: bash

- name: Test
run: go test -v ./...
working-directory: packages/go
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions packages/go/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.test
*.exe
*.out
11 changes: 11 additions & 0 deletions packages/go/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "braillify-go"
version = "2.0.0"
edition = "2024"

[lib]
name = "braillify_go"
crate-type = ["cdylib", "staticlib"]

[dependencies]
braillify = { path = "../../libs/braillify", default-features = false }
16 changes: 16 additions & 0 deletions packages/go/braillify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package braillify

// Encode converts Korean text to braille byte representation.
func Encode(text string) ([]byte, error) {
return cEncode(text)
}

// EncodeToUnicode converts Korean text to braille Unicode string.
func EncodeToUnicode(text string) (string, error) {
return cEncodeToUnicode(text)
}

// EncodeToBrailleFont converts Korean text to braille font string.
func EncodeToBrailleFont(text string) (string, error) {
return cEncodeToBrailleFont(text)
}
51 changes: 51 additions & 0 deletions packages/go/braillify_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package braillify

import "testing"

func TestEncodeToUnicode(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"안녕하세요", "⠣⠒⠉⠻⠚⠠⠝⠬"},
{"상상이상의", "⠇⠶⠇⠶⠕⠇⠶⠺"},
{"1,000", "⠼⠁⠂⠚⠚⠚"},
{"ATM", "⠠⠠⠁⠞⠍"},
{"", ""},
}

for _, tt := range tests {
result, err := EncodeToUnicode(tt.input)
if err != nil {
t.Errorf("EncodeToUnicode(%q): unexpected error: %v", tt.input, err)
continue
}
t.Logf("EncodeToUnicode(%q) = %q", tt.input, result)
if result != tt.expected {
t.Errorf("EncodeToUnicode(%q) = %q, want %q", tt.input, result, tt.expected)
}
}
}

func TestEncode(t *testing.T) {
result, err := Encode("안녕")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
t.Logf("Encode(%q) = %v", "안녕", result)
if len(result) == 0 {
t.Error("expected non-empty byte slice")
}
}

func TestEncodeToBrailleFont(t *testing.T) {
result, err := EncodeToBrailleFont("안녕하세요")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expected := "⠣⠒⠉⠻⠚⠠⠝⠬"
t.Logf("EncodeToBrailleFont(%q) = %q", "안녕하세요", result)
if result != expected {
t.Errorf("EncodeToBrailleFont = %q, want %q", result, expected)
}
}
85 changes: 85 additions & 0 deletions packages/go/cgo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package braillify

/*
#cgo darwin,amd64 LDFLAGS: -L${SRCDIR}/libs/darwin-amd64 -lbraillify_go -lm -lpthread
#cgo darwin,arm64 LDFLAGS: -L${SRCDIR}/libs/darwin-arm64 -lbraillify_go -lm -lpthread
#cgo linux,amd64 LDFLAGS: -L${SRCDIR}/libs/linux-amd64 -lbraillify_go -lm -lpthread -ldl
#cgo linux,arm64 LDFLAGS: -L${SRCDIR}/libs/linux-arm64 -lbraillify_go -lm -lpthread -ldl
#cgo windows,amd64 LDFLAGS: -L${SRCDIR}/libs/windows-amd64 -lbraillify_go -lntdll -lws2_32 -lbcrypt -ladvapi32 -luserenv

#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>

extern uint8_t* braillify_encode(const char* text, size_t* out_len);
extern char* braillify_encode_to_unicode(const char* text);
extern char* braillify_encode_to_braille_font(const char* text);
extern char* braillify_get_last_error();
extern void braillify_free_string(char* ptr);
extern void braillify_free_bytes(uint8_t* ptr, size_t len);
*/
import "C"

import (
"errors"
"runtime"
"unsafe"
)

func cEncode(text string) ([]byte, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()

cText := C.CString(text)
defer C.free(unsafe.Pointer(cText))

var outLen C.size_t
result := C.braillify_encode(cText, &outLen)
if result == nil {
return nil, getLastError()
}
defer C.braillify_free_bytes(result, outLen)

return C.GoBytes(unsafe.Pointer(result), C.int(outLen)), nil
}

func cEncodeToUnicode(text string) (string, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()

cText := C.CString(text)
defer C.free(unsafe.Pointer(cText))

result := C.braillify_encode_to_unicode(cText)
if result == nil {
return "", getLastError()
}
defer C.braillify_free_string(result)

return C.GoString(result), nil
}

func cEncodeToBrailleFont(text string) (string, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()

cText := C.CString(text)
defer C.free(unsafe.Pointer(cText))

result := C.braillify_encode_to_braille_font(cText)
if result == nil {
return "", getLastError()
}
defer C.braillify_free_string(result)

return C.GoString(result), nil
}

func getLastError() error {
errPtr := C.braillify_get_last_error()
if errPtr == nil {
return errors.New("braillify: unknown error")
}
defer C.braillify_free_string(errPtr)
return errors.New(C.GoString(errPtr))
}
3 changes: 3 additions & 0 deletions packages/go/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/dev-five-git/braillify/packages/go

go 1.21
Binary file added packages/go/libs/windows-amd64/libbraillify_go.a
Binary file not shown.
145 changes: 145 additions & 0 deletions packages/go/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
use std::cell::RefCell;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::ptr;

thread_local! {
static LAST_ERROR: RefCell<Option<String>> = const { RefCell::new(None) };
}

fn set_last_error(err: String) {
LAST_ERROR.with(|e| {
*e.borrow_mut() = Some(err);
});
}

fn clear_last_error() {
LAST_ERROR.with(|e| {
*e.borrow_mut() = None;
});
}

#[unsafe(no_mangle)]
pub extern "C" fn braillify_get_last_error() -> *mut c_char {
LAST_ERROR.with(|e| match e.borrow().as_ref() {
Some(msg) => CString::new(msg.clone())
.map(|s| s.into_raw())
.unwrap_or(ptr::null_mut()),
None => ptr::null_mut(),
})
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn braillify_encode(text: *const c_char, out_len: *mut usize) -> *mut u8 {
clear_last_error();

if text.is_null() || out_len.is_null() {
set_last_error("Null pointer argument".to_string());
return ptr::null_mut();
}

let c_str = unsafe { CStr::from_ptr(text) };
let text_str = match c_str.to_str() {
Ok(s) => s,
Err(e) => {
set_last_error(format!("Invalid UTF-8: {}", e));
return ptr::null_mut();
}
};

match braillify::encode(text_str) {
Ok(result) => {
unsafe { *out_len = result.len() };
let boxed = result.into_boxed_slice();
Box::into_raw(boxed) as *mut u8
}
Err(e) => {
set_last_error(e);
ptr::null_mut()
}
}
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn braillify_encode_to_unicode(text: *const c_char) -> *mut c_char {
clear_last_error();

if text.is_null() {
set_last_error("Null pointer argument".to_string());
return ptr::null_mut();
}

let c_str = unsafe { CStr::from_ptr(text) };
let text_str = match c_str.to_str() {
Ok(s) => s,
Err(e) => {
set_last_error(format!("Invalid UTF-8: {}", e));
return ptr::null_mut();
}
};

match braillify::encode_to_unicode(text_str) {
Ok(result) => match CString::new(result) {
Ok(c_string) => c_string.into_raw(),
Err(e) => {
set_last_error(format!("CString conversion error: {}", e));
ptr::null_mut()
}
},
Err(e) => {
set_last_error(e);
ptr::null_mut()
}
}
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn braillify_encode_to_braille_font(text: *const c_char) -> *mut c_char {
clear_last_error();

if text.is_null() {
set_last_error("Null pointer argument".to_string());
return ptr::null_mut();
}

let c_str = unsafe { CStr::from_ptr(text) };
let text_str = match c_str.to_str() {
Ok(s) => s,
Err(e) => {
set_last_error(format!("Invalid UTF-8: {}", e));
return ptr::null_mut();
}
};

match braillify::encode_to_braille_font(text_str) {
Ok(result) => match CString::new(result) {
Ok(c_string) => c_string.into_raw(),
Err(e) => {
set_last_error(format!("CString conversion error: {}", e));
ptr::null_mut()
}
},
Err(e) => {
set_last_error(e);
ptr::null_mut()
}
}
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn braillify_free_string(ptr: *mut c_char) {
if !ptr.is_null() {
unsafe {
drop(CString::from_raw(ptr));
}
}
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn braillify_free_bytes(ptr: *mut u8, len: usize) {
if !ptr.is_null() {
unsafe {
let _ = Vec::from_raw_parts(ptr, len, len);
}
}
}
Loading