Initial commit
This commit is contained in:
commit
f9d3a5e397
|
@ -0,0 +1,9 @@
|
|||
/target
|
||||
|
||||
|
||||
# Added by cargo
|
||||
#
|
||||
# already existing elements were commented out
|
||||
|
||||
#/target
|
||||
/Cargo.lock
|
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "splash-rs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
|
@ -0,0 +1,93 @@
|
|||
fn main() {
|
||||
link_swift();
|
||||
link_swift_package("highlight-swift", "./highlight-swift/");
|
||||
|
||||
let target = get_swift_target_info();
|
||||
// on linux we need to tell it to link against the swift stdlib,
|
||||
// because the --static-swift-stdlib on swift build doesn't work for static libraries
|
||||
// on macos, it all magically works
|
||||
if target.target.unversioned_triple.contains("linux") {
|
||||
// include the swift runtime library path in the rpath
|
||||
target.paths.runtime_library_paths.iter().for_each(|path| {
|
||||
println!("cargo:rustc-link-arg-Wl,-rpath={}", path);
|
||||
});
|
||||
// need to link against libswiftCore and libFoundation
|
||||
println!("cargo:rustc-link-lib=dylib=swiftCore");
|
||||
println!("cargo:rustc-link-lib=dylib=Foundation");
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from https://github.com/Brendonovich/swift-rs
|
||||
// With get_swift_target_info changed to print the current target
|
||||
// rather than always trying for the macosx target.
|
||||
|
||||
use std::{env, process::Command};
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SwiftTargetInfo {
|
||||
pub unversioned_triple: String,
|
||||
#[serde(rename = "librariesRequireRPath")]
|
||||
pub libraries_require_rpath: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SwiftPaths {
|
||||
pub runtime_library_paths: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SwiftTarget {
|
||||
pub target: SwiftTargetInfo,
|
||||
pub paths: SwiftPaths,
|
||||
}
|
||||
|
||||
pub fn get_swift_target_info() -> SwiftTarget {
|
||||
let swift_target_info_str = Command::new("swift")
|
||||
.args(&["-print-target-info"])
|
||||
.output()
|
||||
.unwrap()
|
||||
.stdout;
|
||||
|
||||
serde_json::from_slice(&swift_target_info_str).unwrap()
|
||||
}
|
||||
|
||||
pub fn link_swift() {
|
||||
let swift_target_info = get_swift_target_info();
|
||||
if swift_target_info.target.libraries_require_rpath {
|
||||
panic!("Libraries require RPath! Change minimum MacOS value to fix.")
|
||||
}
|
||||
|
||||
swift_target_info
|
||||
.paths
|
||||
.runtime_library_paths
|
||||
.iter()
|
||||
.for_each(|path| {
|
||||
println!("cargo:rustc-link-search=native={}", path);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn link_swift_package(package_name: &str, package_root: &str) {
|
||||
let profile = env::var("PROFILE").unwrap();
|
||||
|
||||
if !Command::new("swift")
|
||||
.args(&["build", "-c", &profile])
|
||||
.current_dir(package_root)
|
||||
.status()
|
||||
.unwrap()
|
||||
.success()
|
||||
{
|
||||
panic!("Failed to compile swift package {}", package_name);
|
||||
}
|
||||
|
||||
let swift_target_info = get_swift_target_info();
|
||||
|
||||
println!(
|
||||
"cargo:rustc-link-search=native={}.build/{}/{}",
|
||||
package_root, swift_target_info.target.unversioned_triple, profile
|
||||
);
|
||||
println!("cargo:rustc-link-lib=static={}", package_name);
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "splash",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/JohnSundell/Splash.git",
|
||||
"state" : {
|
||||
"revision" : "7f4df436eb78fe64fe2c32c58006e9949fa28ad8",
|
||||
"version" : "0.16.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
// swift-tools-version: 5.6
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "highlight-swift",
|
||||
platforms: [
|
||||
.macOS(.v11),
|
||||
],
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||
.library(
|
||||
name: "highlight-swift",
|
||||
type: .static,
|
||||
targets: ["highlight-swift"]),
|
||||
],
|
||||
dependencies: [
|
||||
// Dependencies declare other packages that this package depends on.
|
||||
.package(url: "https://github.com/JohnSundell/Splash.git", .upToNextMinor(from: "0.16.0")),
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||
.target(
|
||||
name: "highlight-swift",
|
||||
dependencies: ["Splash"]),
|
||||
]
|
||||
)
|
|
@ -0,0 +1,3 @@
|
|||
# highlight-swift
|
||||
|
||||
A description of this package.
|
|
@ -0,0 +1,120 @@
|
|||
import Foundation
|
||||
import Splash
|
||||
|
||||
@_cdecl("highlight_swift")
|
||||
public func highlight(codePtr: UnsafeRawPointer, codeLen: UInt64, outPtr: UnsafeMutableRawPointer, maxLen: UInt64) -> UInt64 {
|
||||
// don't free, the underlying data is owned by rust
|
||||
let code = String(bytesNoCopy: UnsafeMutableRawPointer(mutating: codePtr), length: Int(codeLen), encoding: .utf8, freeWhenDone: false)!
|
||||
|
||||
let highligher = SyntaxHighlighter(format: MyOutputFormat())
|
||||
var html = highligher.highlight(code)
|
||||
precondition(html.utf8.count <= maxLen)
|
||||
return html.withUTF8 { buf in
|
||||
buf.copyBytes(to: UnsafeMutableRawBufferPointer(start: outPtr, count: Int(maxLen)))
|
||||
return UInt64(buf.count)
|
||||
}
|
||||
}
|
||||
|
||||
struct MyOutputFormat: OutputFormat {
|
||||
func makeBuilder() -> Builder {
|
||||
return Builder()
|
||||
}
|
||||
|
||||
struct Builder: OutputBuilder {
|
||||
typealias Output = String
|
||||
|
||||
private var html = ""
|
||||
private var pendingToken: (string: String, type: TokenType)?
|
||||
private var pendingWhitespace: String?
|
||||
|
||||
mutating func addToken(_ token: String, ofType type: TokenType) {
|
||||
if var pendingToken = pendingToken {
|
||||
guard pendingToken.type != type else {
|
||||
pendingWhitespace.map { pendingToken.string += $0 }
|
||||
pendingWhitespace = nil
|
||||
pendingToken.string += token
|
||||
self.pendingToken = pendingToken
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
appendPending()
|
||||
pendingToken = (token, type)
|
||||
}
|
||||
|
||||
mutating func addPlainText(_ text: String) {
|
||||
appendPending()
|
||||
html.append(contentsOf: text.escapingHTMLEntities())
|
||||
}
|
||||
|
||||
mutating func addWhitespace(_ whitespace: String) {
|
||||
if pendingToken != nil {
|
||||
pendingWhitespace = (pendingWhitespace ?? "") + whitespace
|
||||
} else {
|
||||
html.append(whitespace)
|
||||
}
|
||||
}
|
||||
|
||||
mutating func build() -> String {
|
||||
appendPending()
|
||||
return html
|
||||
}
|
||||
|
||||
private mutating func appendPending() {
|
||||
if let pendingToken = pendingToken {
|
||||
let cls: String
|
||||
switch pendingToken.type {
|
||||
case .keyword:
|
||||
cls = "hl-kw"
|
||||
case .string:
|
||||
cls = "hl-str"
|
||||
case .type:
|
||||
cls = "hl-type"
|
||||
case .call:
|
||||
cls = "hl-fn"
|
||||
case .number:
|
||||
cls = "hl-num"
|
||||
case .comment:
|
||||
cls = "hl-cmt"
|
||||
case .property:
|
||||
cls = "hl-var"
|
||||
case .dotAccess:
|
||||
cls = "hl-prop"
|
||||
case .preprocessing:
|
||||
cls = ""
|
||||
case .custom(_):
|
||||
cls = ""
|
||||
}
|
||||
html.append("""
|
||||
<span class="\(cls)">\(pendingToken.string.escapingHTMLEntities())</span>
|
||||
""")
|
||||
|
||||
self.pendingToken = nil
|
||||
}
|
||||
|
||||
if let pendingWhitespace = pendingWhitespace {
|
||||
html.append(pendingWhitespace)
|
||||
self.pendingWhitespace = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// copied from Splash, where it's internal
|
||||
extension StringProtocol {
|
||||
func escapingHTMLEntities() -> String {
|
||||
return String(flatMap { character -> String in
|
||||
switch character {
|
||||
case "&":
|
||||
return "&"
|
||||
case "<":
|
||||
return "<"
|
||||
case ">":
|
||||
return ">"
|
||||
default:
|
||||
return String(character)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
pub fn highlight(code: &str) -> String {
|
||||
unsafe {
|
||||
let mut buf = Vec::new();
|
||||
buf.reserve(
|
||||
2usize
|
||||
.pow(1 + (code.len() as f32).log2().ceil() as u32)
|
||||
.max(1024),
|
||||
);
|
||||
let used = highlight_swift(
|
||||
code.as_ptr(),
|
||||
code.len() as u64,
|
||||
buf.as_mut_ptr(),
|
||||
buf.capacity() as u64,
|
||||
);
|
||||
buf.set_len(used as usize);
|
||||
String::from_utf8_unchecked(buf)
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn highlight_swift(code_ptr: *const u8, code_len: u64, out_ptr: *mut u8, max_len: u64) -> u64;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn test_highlight() {
|
||||
let result = super::highlight("1+1");
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"<span class="hl-num">1</span>+<span class="hl-num">1</span>"#
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue