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