@tishlang/tish 2.0.3 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/tish CHANGED
Binary file
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "tishlang"
3
- version = "2.0.3"
3
+ version = "2.2.0"
4
4
  edition = "2021"
5
5
  description = "Tish CLI - run, REPL, compile to native"
6
6
  license-file = { workspace = true }
@@ -0,0 +1,10 @@
1
+ // Fixture for issue #120: read a binary file as a byte array via tish:fs.readFileBytes.
2
+ // The test points RFB_FIXTURE at a file containing non-UTF-8 bytes (NUL, 0xFF, …).
3
+ import { readFileBytes } from "tish:fs"
4
+ import { process } from "tish:process"
5
+
6
+ let path = process.env.RFB_FIXTURE
7
+ let b = readFileBytes(path)
8
+ console.log("isArray:" + String(Array.isArray(b)))
9
+ console.log("len:" + String(b.length))
10
+ console.log("bytes:" + b.join(","))
@@ -0,0 +1,45 @@
1
+ //! Issue #120: `tish:fs.readFileBytes` reads a binary file as an array of byte values (0–255)
2
+ //! on every backend, where the UTF-8-only `readFile` cannot. Verifies the exact bytes,
3
+ //! including a NUL and 0xFF (not valid UTF-8).
4
+
5
+ use std::path::PathBuf;
6
+ use std::process::Command;
7
+
8
+ const EXPECTED: &str = "\
9
+ isArray:true
10
+ len:7
11
+ bytes:0,1,2,255,128,72,73
12
+ ";
13
+
14
+ fn run(backend: &str, fixture: &str, data_path: &str) -> String {
15
+ let tish = PathBuf::from(env!("CARGO_BIN_EXE_tish"));
16
+ let out = Command::new(&tish)
17
+ .args(["run", "--backend", backend, "--feature", "fs,process", fixture])
18
+ .env("RFB_FIXTURE", data_path)
19
+ .output()
20
+ .expect("spawn tish run");
21
+ assert!(
22
+ out.status.success(),
23
+ "tish run --backend {backend} failed: {}",
24
+ String::from_utf8_lossy(&out.stderr)
25
+ );
26
+ String::from_utf8_lossy(&out.stdout).into_owned()
27
+ }
28
+
29
+ #[test]
30
+ fn read_file_bytes_reads_binary_on_every_backend() {
31
+ // A file whose bytes are not valid UTF-8 (NUL, 0xFF, 0x80) — readFile would error on it.
32
+ let data_path = std::env::temp_dir().join("tish_rfb_fixture.dat");
33
+ std::fs::write(&data_path, [0u8, 1, 2, 255, 128, 72, 73]).expect("write fixture");
34
+ let data_path = data_path.to_str().unwrap();
35
+
36
+ let fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
37
+ .join("tests")
38
+ .join("fixtures")
39
+ .join("read_file_bytes.tish");
40
+ assert!(fixture.is_file(), "missing fixture {}", fixture.display());
41
+ let fixture = fixture.to_str().unwrap();
42
+
43
+ assert_eq!(run("vm", fixture, data_path), EXPECTED, "vm output mismatch");
44
+ assert_eq!(run("interp", fixture, data_path), EXPECTED, "interp output mismatch");
45
+ }
@@ -1094,6 +1094,7 @@ impl Codegen {
1094
1094
  "fileExists" => Some("Value::native(|args: &[Value]| tish_file_exists(args))"),
1095
1095
  "isDir" => Some("Value::native(|args: &[Value]| tish_is_dir(args))"),
1096
1096
  "readDir" => Some("Value::native(|args: &[Value]| tish_read_dir(args))"),
1097
+ "readFileBytes" => Some("Value::native(|args: &[Value]| tish_read_file_bytes(args))"),
1097
1098
  "mkdir" => Some("Value::native(|args: &[Value]| tish_mkdir(args))"),
1098
1099
  _ => None,
1099
1100
  },
@@ -1455,7 +1456,7 @@ impl Codegen {
1455
1456
  }
1456
1457
  }
1457
1458
  if self.has_feature("fs") {
1458
- self.write("use tishlang_runtime::{read_file as tish_read_file, write_file as tish_write_file, file_exists as tish_file_exists, is_dir as tish_is_dir, read_dir as tish_read_dir, mkdir as tish_mkdir};\n");
1459
+ self.write("use tishlang_runtime::{read_file as tish_read_file, read_file_bytes as tish_read_file_bytes, write_file as tish_write_file, file_exists as tish_file_exists, is_dir as tish_is_dir, read_dir as tish_read_dir, mkdir as tish_mkdir};\n");
1459
1460
  }
1460
1461
  if self.has_feature("ws") {
1461
1462
  self.write("use tishlang_runtime::{web_socket_client as tish_ws_client, web_socket_server_construct as tish_ws_server_construct};\n");
@@ -916,6 +916,10 @@ impl Evaluator {
916
916
  exports.insert("fileExists".into(), Value::Native(natives::file_exists));
917
917
  exports.insert("isDir".into(), Value::Native(natives::is_dir));
918
918
  exports.insert("readDir".into(), Value::Native(natives::read_dir));
919
+ exports.insert(
920
+ "readFileBytes".into(),
921
+ Value::Native(natives::read_file_bytes),
922
+ );
919
923
  exports.insert("mkdir".into(), Value::Native(natives::mkdir));
920
924
  Ok(Value::object(exports))
921
925
  }
@@ -420,6 +420,22 @@ pub fn read_file(args: &[Value]) -> Result<Value, String> {
420
420
  }
421
421
  }
422
422
 
423
+ /// Read a file as raw bytes (array of numbers 0–255) for binary data that `read_file`
424
+ /// (UTF-8 only) can't handle. See issue #120.
425
+ #[cfg(feature = "fs")]
426
+ pub fn read_file_bytes(args: &[Value]) -> Result<Value, String> {
427
+ use std::cell::RefCell;
428
+ use std::rc::Rc;
429
+ let path = args.first().map(|v| v.to_string()).unwrap_or_default();
430
+ match std::fs::read(&path) {
431
+ Ok(bytes) => {
432
+ let items: Vec<Value> = bytes.into_iter().map(|b| Value::Number(b as f64)).collect();
433
+ Ok(Value::Array(Rc::new(RefCell::new(items))))
434
+ }
435
+ Err(e) => Ok(Value::String(format!("Error: {}", e).into())),
436
+ }
437
+ }
438
+
423
439
  #[cfg(feature = "fs")]
424
440
  pub fn write_file(args: &[Value]) -> Result<Value, String> {
425
441
  let path = args.first().map(|v| v.to_string()).unwrap_or_default();
@@ -666,6 +666,23 @@ pub fn read_file(args: &[Value]) -> Value {
666
666
  }
667
667
  }
668
668
 
669
+ /// Read a file as raw bytes — an array of numbers 0–255 — for binary data that `read_file`
670
+ /// (UTF-8 only) can't handle (images, fonts, archives). Lets pure-Tish decoders work on local
671
+ /// files. See issue #120.
672
+ #[cfg(feature = "fs")]
673
+ pub fn read_file_bytes(args: &[Value]) -> Value {
674
+ let path = args
675
+ .first()
676
+ .map(|v| v.to_display_string())
677
+ .unwrap_or_default();
678
+ match std::fs::read(&path) {
679
+ Ok(bytes) => Value::Array(VmRef::new(
680
+ bytes.into_iter().map(|b| Value::Number(b as f64)).collect(),
681
+ )),
682
+ Err(e) => make_error_value(e),
683
+ }
684
+ }
685
+
669
686
  #[cfg(feature = "fs")]
670
687
  pub fn write_file(args: &[Value]) -> Value {
671
688
  let path = args
@@ -169,6 +169,9 @@ fn get_builtin_export(enabled: &HashSet<String>, spec: &str, export_name: &str)
169
169
  "mkdir" => Some(Value::native(|args: &[Value]| {
170
170
  tishlang_runtime::mkdir(args)
171
171
  })),
172
+ "readFileBytes" => Some(Value::native(|args: &[Value]| {
173
+ tishlang_runtime::read_file_bytes(args)
174
+ })),
172
175
  _ => None,
173
176
  };
174
177
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tishlang/tish",
3
- "version": "2.0.3",
3
+ "version": "2.2.0",
4
4
  "description": "Tish - minimal TS/JS-compatible language. Run, REPL, build to native or other targets.",
5
5
  "license": "PIF",
6
6
  "repository": {
Binary file
Binary file
Binary file
Binary file
Binary file