buninu 0.4.17 → 0.4.19
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/CHANGELOG.md +9 -0
- package/apps/bunmsh/CHANGELOG.md +43 -0
- package/apps/bunmsh/PORTING.md +6 -2
- package/apps/bunmsh/help/README.md +3 -1
- package/apps/bunmsh/help/ln.md +3 -1
- package/apps/bunmsh/package.json +1 -1
- package/apps/bunmsh/src/main.js +7 -5
- package/apps/bunmsh/src/shell.js +757 -159
- package/apps/bunmsh/test/reference.test.js +95 -1
- package/apps/bunmsh/test/shell1.test.js +155 -0
- package/apps/jsgotty/LICENSES/MIT-microsoft-terminal-conpty.txt +21 -0
- package/apps/jsgotty/LICENSES/MIT-ws.txt +20 -0
- package/apps/jsgotty/LICENSES/NOTICE-microsoft-terminal-conpty.md +535 -0
- package/apps/jsgotty/NOTICE +100 -0
- package/apps/jsgotty/conpty/OpenConsole.exe +0 -0
- package/apps/jsgotty/conpty/conpty.dll +0 -0
- package/apps/jsgotty/gotty.js +24 -0
- package/apps/jsgotty/package-lock.json +2 -2
- package/apps/jsgotty/package.json +6 -2
- package/apps/jsgotty/windows-conpty.js +135 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
-
import { chmodSync, mkdirSync, mkdtempSync, rmSync } from "node:fs";
|
|
2
|
+
import { chmodSync, mkdirSync, mkdtempSync, readlinkSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { createState, decode, execute } from "../src/shell.js";
|
|
@@ -86,6 +86,15 @@ async function expectBuiltinSedLike(args, input, cwd = root) {
|
|
|
86
86
|
expect(actual).toEqual(reference);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
async function expectBuiltinLikeSystem(name, args, input = "", cwd = root) {
|
|
90
|
+
const [actual, reference] = await Promise.all([
|
|
91
|
+
invokeArgvWithInput(
|
|
92
|
+
[process.execPath, join(root, "src/main.js"), "-cc", "builtin", name, ...args], input, cwd),
|
|
93
|
+
invokeArgvWithInput([Bun.which(name), ...args], input, cwd),
|
|
94
|
+
]);
|
|
95
|
+
expect(actual).toEqual(reference);
|
|
96
|
+
}
|
|
97
|
+
|
|
89
98
|
describe("/bin/sh reference", () => {
|
|
90
99
|
describe("quote and word expansion", () => {
|
|
91
100
|
test("removes LF and CRLF backslash continuations before tokenization", async () => {
|
|
@@ -402,6 +411,79 @@ printf ':%s' $?
|
|
|
402
411
|
});
|
|
403
412
|
});
|
|
404
413
|
|
|
414
|
+
describe("system multi-operand reference", () => {
|
|
415
|
+
test("printf repeats its format until all arguments are consumed", async () => {
|
|
416
|
+
await expectBuiltinLikeSystem("printf", ["%s=%d\n", "one", "1", "two", "2"]);
|
|
417
|
+
await expectBuiltinLikeSystem("printf", ["[%s][%s]\n", "one", "two", "three"]);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
test("ln links each source into a target directory", async () => {
|
|
421
|
+
const directory = mkdtempSync(join(tmpdir(), "bunmsh-ln-reference-"));
|
|
422
|
+
const actualDir = join(directory, "actual");
|
|
423
|
+
const systemDir = join(directory, "system");
|
|
424
|
+
try {
|
|
425
|
+
for (const cwd of [actualDir, systemDir]) {
|
|
426
|
+
mkdirSync(cwd);
|
|
427
|
+
mkdirSync(join(cwd, "hard"));
|
|
428
|
+
mkdirSync(join(cwd, "soft"));
|
|
429
|
+
await Bun.write(join(cwd, "one"), "first\n");
|
|
430
|
+
await Bun.write(join(cwd, "two"), "second\n");
|
|
431
|
+
}
|
|
432
|
+
for (const args of [["one", "two", "hard"], ["-s", "one", "two", "soft"]]) {
|
|
433
|
+
const [actual, reference] = await Promise.all([
|
|
434
|
+
invokeArgvWithInput([process.execPath, join(root, "src/main.js"), "-cc", "builtin", "ln", ...args], "", actualDir),
|
|
435
|
+
invokeArgvWithInput([Bun.which("ln"), ...args], "", systemDir),
|
|
436
|
+
]);
|
|
437
|
+
expect(actual).toEqual(reference);
|
|
438
|
+
}
|
|
439
|
+
for (const name of ["one", "two"]) {
|
|
440
|
+
expect(readdirSync(join(actualDir, "hard"))).toEqual(readdirSync(join(systemDir, "hard")));
|
|
441
|
+
expect(statSync(join(actualDir, "hard", name)).ino).toBe(statSync(join(actualDir, name)).ino);
|
|
442
|
+
expect(readlinkSync(join(actualDir, "soft", name)))
|
|
443
|
+
.toBe(readlinkSync(join(systemDir, "soft", name)));
|
|
444
|
+
}
|
|
445
|
+
} finally { rmSync(directory, { recursive: true, force: true }); }
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
test("head and tail select lines independently from each file", async () => {
|
|
449
|
+
const directory = mkdtempSync(join(tmpdir(), "bunmsh-parts-reference-"));
|
|
450
|
+
try {
|
|
451
|
+
await Bun.write(join(directory, "one"), "alpha\nbeta\n");
|
|
452
|
+
await Bun.write(join(directory, "two"), "gamma\ndelta\n");
|
|
453
|
+
for (const args of [["-n", "1", "one", "two"], ["-c", "2", "one", "two"],
|
|
454
|
+
["-n", "0", "one", "two"]])
|
|
455
|
+
await expectBuiltinLikeSystem("head", args, "", directory);
|
|
456
|
+
for (const args of [["-n", "1", "one", "two"], ["-n", "+2", "one", "two"],
|
|
457
|
+
["-n", "0", "one", "two"]])
|
|
458
|
+
await expectBuiltinLikeSystem("tail", args, "", directory);
|
|
459
|
+
} finally { rmSync(directory, { recursive: true, force: true }); }
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
test("wc prints each file count and a total; cut reads each file", async () => {
|
|
463
|
+
const directory = mkdtempSync(join(tmpdir(), "bunmsh-text-reference-"));
|
|
464
|
+
try {
|
|
465
|
+
await Bun.write(join(directory, "one"), "alpha beta\ngamma\n");
|
|
466
|
+
await Bun.write(join(directory, "two"), "delta\nepsilon zeta\n");
|
|
467
|
+
for (const args of [["-l", "one", "two"], ["-lwc", "one", "two"]])
|
|
468
|
+
await expectBuiltinLikeSystem("wc", args, "", directory);
|
|
469
|
+
await expectBuiltinLikeSystem("cut", ["-c", "1-3", "one", "two"], "", directory);
|
|
470
|
+
await expectBuiltinLikeSystem("cut", ["-c", "1", "one"], "", directory);
|
|
471
|
+
} finally { rmSync(directory, { recursive: true, force: true }); }
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
test("md5sum and sha256sum hash each file separately", async () => {
|
|
475
|
+
const directory = mkdtempSync(join(tmpdir(), "bunmsh-hash-reference-"));
|
|
476
|
+
try {
|
|
477
|
+
await Bun.write(join(directory, "one"), "alpha\n");
|
|
478
|
+
await Bun.write(join(directory, "two"), "beta\n");
|
|
479
|
+
for (const name of ["md5sum", "sha256sum"]) {
|
|
480
|
+
await expectBuiltinLikeSystem(name, ["one", "two"], "", directory);
|
|
481
|
+
await expectBuiltinLikeSystem(name, [], "stdin text\n", directory);
|
|
482
|
+
}
|
|
483
|
+
} finally { rmSync(directory, { recursive: true, force: true }); }
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
|
|
405
487
|
describe("system tail reference", () => {
|
|
406
488
|
test("supports -n +N and compact -n+N from-start modes", async () => {
|
|
407
489
|
const input = "one\ntwo\nthree\nfour\n";
|
|
@@ -450,6 +532,18 @@ describe("system grep reference", () => {
|
|
|
450
532
|
await expectBuiltinGrepLike(["-n", "needle", "tree/a.txt", "tree/nested/b.txt"], "", directory);
|
|
451
533
|
} finally { rmSync(directory, { recursive: true, force: true }); }
|
|
452
534
|
});
|
|
535
|
+
|
|
536
|
+
test("searches the current directory for -r without a path", async () => {
|
|
537
|
+
const directory = mkdtempSync(join(tmpdir(), "bunmsh-grep-cwd-reference-"));
|
|
538
|
+
try {
|
|
539
|
+
mkdirSync(join(directory, "nested"));
|
|
540
|
+
await Bun.write(join(directory, "nested", "a.txt"), "needle one\nnone\n");
|
|
541
|
+
await expectBuiltinGrepLike(["-r", "needle"], "", directory);
|
|
542
|
+
await expectBuiltinGrepLike(["-rn", "needle"], "", directory);
|
|
543
|
+
await expectBuiltinGrepLike(["-r", "missing"], "", directory);
|
|
544
|
+
await expectBuiltinGrepLike(["-rq", "needle"], "", directory);
|
|
545
|
+
} finally { rmSync(directory, { recursive: true, force: true }); }
|
|
546
|
+
});
|
|
453
547
|
});
|
|
454
548
|
|
|
455
549
|
describe("system sed reference", () => {
|
|
@@ -93,6 +93,28 @@ describe("parser", () => {
|
|
|
93
93
|
expect(parse("echo hi | tr a-z A-Z")[0].pipeline).toHaveLength(2);
|
|
94
94
|
});
|
|
95
95
|
|
|
96
|
+
test("parses a trailing background operator", () => {
|
|
97
|
+
const [job] = parse("sleep 30 &");
|
|
98
|
+
expect(job.background).toBe(true);
|
|
99
|
+
expect(job.pipeline).toHaveLength(1);
|
|
100
|
+
const [foreground] = parse("echo before & after");
|
|
101
|
+
expect(foreground.background).toBe(false);
|
|
102
|
+
expect(foreground.pipeline[0].words[2].fragments[0].text).toBe("&");
|
|
103
|
+
expect(parse(`echo "&"`)[0].background).toBe(false);
|
|
104
|
+
expect(parse("echo \\&")[0].background).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("recognizes a trailing & from tokens, independently of comments and redirect targets", () => {
|
|
108
|
+
for (const source of ["sleep 30 & # comment", "sleep 30 & # comment &", "sleep 30 &\r\n# comment\r\n"]) {
|
|
109
|
+
const [job] = parse(source);
|
|
110
|
+
expect(job.background).toBe(true);
|
|
111
|
+
expect(job.pipeline[0].words).toHaveLength(2);
|
|
112
|
+
}
|
|
113
|
+
for (const source of ["echo & > file&", "echo & <<< input&", "echo &; # comment &", "echo hi # &"]) {
|
|
114
|
+
expect(parse(source)[0].background).toBe(false);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
96
118
|
test("a bare trailing backslash asks for one more interactive line, then joins", () => {
|
|
97
119
|
// This is what the interactive prompt loop's `pending` string looks like
|
|
98
120
|
// the instant Enter is pressed after typing "echo hi \" -- the next
|
|
@@ -398,6 +420,69 @@ describe("execution", () => {
|
|
|
398
420
|
});
|
|
399
421
|
});
|
|
400
422
|
|
|
423
|
+
test("parses POSIX case clauses independently of physical lines", async () => {
|
|
424
|
+
expect(await run('case "a b" in a\\ b|no) echo escaped;; *) echo bad;; esac')).toMatchObject({
|
|
425
|
+
status: 0,
|
|
426
|
+
stdout: "escaped\n",
|
|
427
|
+
stderr: "",
|
|
428
|
+
});
|
|
429
|
+
expect(await run("case x in (no) echo bad ;; (x) echo parenthesized\nesac")).toMatchObject({
|
|
430
|
+
status: 0,
|
|
431
|
+
stdout: "parenthesized\n",
|
|
432
|
+
stderr: "",
|
|
433
|
+
});
|
|
434
|
+
expect(await run("case x in x) case y in y) echo nested;; esac ;; *) echo bad;; esac")).toMatchObject({
|
|
435
|
+
status: 0,
|
|
436
|
+
stdout: "nested\n",
|
|
437
|
+
stderr: "",
|
|
438
|
+
});
|
|
439
|
+
expect(await run("case outer in outer) case inner in inner) echo autoconf;; esac;; *) echo bad;; esac")).toMatchObject({
|
|
440
|
+
status: 0,
|
|
441
|
+
stdout: "autoconf\n",
|
|
442
|
+
stderr: "",
|
|
443
|
+
});
|
|
444
|
+
expect(await run("case x in x) echo case;; esac; echo after")).toMatchObject({
|
|
445
|
+
status: 0,
|
|
446
|
+
stdout: "case\nafter\n",
|
|
447
|
+
stderr: "",
|
|
448
|
+
});
|
|
449
|
+
expect(await run("case b in [!ac]) echo bracket;; *) echo bad;; esac; case '*' in '*') echo quoted;; esac")).toMatchObject({
|
|
450
|
+
stdout: "bracket\nquoted\n",
|
|
451
|
+
});
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
test("implements mksh case fall-through terminators", async () => {
|
|
455
|
+
expect(await run("case b in a) echo a ;& b) echo b ;& c) echo c ;; esac")).toMatchObject({
|
|
456
|
+
stdout: "b\nc\n",
|
|
457
|
+
});
|
|
458
|
+
expect(await run("case b in a) echo a ;| b) echo b ;| *) echo star ;; esac")).toMatchObject({
|
|
459
|
+
stdout: "b\nstar\n",
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
test("accepts POSIX for headers with do on the next line", async () => {
|
|
464
|
+
expect(await run("for value in one two\ndo\n echo $value\ndone")).toMatchObject({
|
|
465
|
+
status: 0,
|
|
466
|
+
stdout: "one\ntwo\n",
|
|
467
|
+
stderr: "",
|
|
468
|
+
});
|
|
469
|
+
expect(await run("set -- left right; for arg\ndo echo $arg; done")).toMatchObject({
|
|
470
|
+
status: 0,
|
|
471
|
+
stdout: "left\nright\n",
|
|
472
|
+
stderr: "",
|
|
473
|
+
});
|
|
474
|
+
expect(await run("set -- a b; for arg; do printf '<%s>\\n' \"$arg\"; done")).toMatchObject({
|
|
475
|
+
status: 0,
|
|
476
|
+
stdout: "<a>\n<b>\n",
|
|
477
|
+
stderr: "",
|
|
478
|
+
});
|
|
479
|
+
expect(await run("for value in; do echo bad; done; echo empty")).toMatchObject({
|
|
480
|
+
status: 0,
|
|
481
|
+
stdout: "empty\n",
|
|
482
|
+
stderr: "",
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
|
|
401
486
|
test("provides basic fallback text and utility commands", async () => {
|
|
402
487
|
expect(await run("printf 'c\\na\\nb\\n' | builtin sort")).toMatchObject({ stdout: "a\nb\nc\n" });
|
|
403
488
|
expect(await run("printf 'one two\\nthree\\n' | builtin wc -lwc")).toMatchObject({ stdout: "2 3 14\n" });
|
|
@@ -911,6 +996,7 @@ fi
|
|
|
911
996
|
test("expands variables while respecting single quotes", async () => {
|
|
912
997
|
const output = await run(`X=world; print -r -- "hello $X"; print -r -- '$X'`);
|
|
913
998
|
expect(output.stdout).toBe("hello world\n$X\n");
|
|
999
|
+
expect((await run("printf '%s\\n' \"literal\\`backtick\"")).stdout).toBe("literal`backtick\n");
|
|
914
1000
|
});
|
|
915
1001
|
|
|
916
1002
|
test("supports status connectors", async () => {
|
|
@@ -926,6 +1012,75 @@ fi
|
|
|
926
1012
|
expect(output.status).toBe(0);
|
|
927
1013
|
});
|
|
928
1014
|
|
|
1015
|
+
test("returns immediately and exposes a background external process through Bun.sha.procs", async () => {
|
|
1016
|
+
const previous = Bun.sha.procs;
|
|
1017
|
+
Bun.sha.procs = [];
|
|
1018
|
+
try {
|
|
1019
|
+
const started = performance.now();
|
|
1020
|
+
const output = await run(`${process.execPath} -e 'await Bun.sleep(30000)' &`);
|
|
1021
|
+
const first = Bun.sha.procs.at(-1);
|
|
1022
|
+
const secondOutput = await run(`${process.execPath} -e 'await Bun.sleep(30000)' &`);
|
|
1023
|
+
const proc = Bun.sha.procs.at(-1);
|
|
1024
|
+
|
|
1025
|
+
expect(performance.now() - started).toBeLessThan(1000);
|
|
1026
|
+
expect(output.status).toBe(0);
|
|
1027
|
+
expect(output.stderr).toBe(`[1] ${first.pid} Bun.sha.procs[0]\n`);
|
|
1028
|
+
expect(secondOutput.status).toBe(0);
|
|
1029
|
+
expect(secondOutput.stderr).toBe(`[2] ${proc.pid} Bun.sha.procs[1]\n`);
|
|
1030
|
+
expect(Bun.sha.procs).toHaveLength(2);
|
|
1031
|
+
expect(proc).not.toBe(first);
|
|
1032
|
+
expect(proc.pid).toBeGreaterThan(0);
|
|
1033
|
+
expect(typeof proc.kill).toBe("function");
|
|
1034
|
+
first.kill();
|
|
1035
|
+
proc.kill();
|
|
1036
|
+
await Promise.all([first.exited, proc.exited]);
|
|
1037
|
+
} finally {
|
|
1038
|
+
if (previous === undefined) delete Bun.sha.procs;
|
|
1039
|
+
else Bun.sha.procs = previous;
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
test("keeps the minimal background form limited to one external command", async () => {
|
|
1044
|
+
const builtin = await run("echo nope &");
|
|
1045
|
+
const pipeline = await run("printf x | cat &");
|
|
1046
|
+
expect(builtin.status).toBe(2);
|
|
1047
|
+
expect(pipeline.status).toBe(2);
|
|
1048
|
+
expect(builtin.stderr).toContain("only a single external command");
|
|
1049
|
+
expect(pipeline.stderr).toContain("only a single external command");
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
test("rejects background redirects before expanding, opening files, or spawning", async () => {
|
|
1053
|
+
const directory = mkdtempSync(join(tmpdir(), "bunmsh-background-redirect-"));
|
|
1054
|
+
const previousProcs = Bun.sha.procs;
|
|
1055
|
+
const previousCount = previousProcs?.length;
|
|
1056
|
+
try {
|
|
1057
|
+
await Bun.write(join(directory, "input"), "input content\n");
|
|
1058
|
+
await Bun.write(join(directory, "output"), "keep this content\n");
|
|
1059
|
+
const command = `'${process.execPath.replaceAll("'", "'\\''")}' -e 'await Bun.write("spawned", "ran")'`;
|
|
1060
|
+
const sources = [
|
|
1061
|
+
...["> output", ">> output", "2> output", "2>> output", "> new-output", "< input", "< missing-input", "<<< payload", "1>&2", "2>&1"]
|
|
1062
|
+
.map((redirect) => `${command} ${redirect} & # comment`),
|
|
1063
|
+
`${command} <<EOF &\nbody\nEOF\n`,
|
|
1064
|
+
`${command} <<-EOF &\n\tbody\n\tEOF\n`,
|
|
1065
|
+
`${command} > $(echo expanded > expanded-marker; echo output) &`,
|
|
1066
|
+
];
|
|
1067
|
+
for (const source of sources) {
|
|
1068
|
+
const output = await run(source, { cwd: directory });
|
|
1069
|
+
expect(output.status).toBe(2);
|
|
1070
|
+
expect(output.stdout).toBe("");
|
|
1071
|
+
expect(output.stderr).toContain("without redirections");
|
|
1072
|
+
}
|
|
1073
|
+
expect(await Bun.file(join(directory, "output")).text()).toBe("keep this content\n");
|
|
1074
|
+
expect(await Bun.file(join(directory, "input")).text()).toBe("input content\n");
|
|
1075
|
+
for (const name of ["spawned", "new-output", "expanded-marker"])
|
|
1076
|
+
expect(await Bun.file(join(directory, name)).exists()).toBe(false);
|
|
1077
|
+
expect(Bun.sha.procs).toBe(previousProcs);
|
|
1078
|
+
expect(Bun.sha.procs?.length).toBe(previousCount);
|
|
1079
|
+
} finally {
|
|
1080
|
+
rmSync(directory, { recursive: true, force: true });
|
|
1081
|
+
}
|
|
1082
|
+
});
|
|
1083
|
+
|
|
929
1084
|
test("keeps builtin state outside pipelines", async () => {
|
|
930
1085
|
const output = await run("export BUNMSH_TEST=value; print -r -- $BUNMSH_TEST");
|
|
931
1086
|
expect(output.stdout).toBe("value\n");
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
|
2
|
+
Copyright (c) 2013 Arnout Kazemier and contributors
|
|
3
|
+
Copyright (c) 2016 Luigi Pinca and contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
7
|
+
the Software without restriction, including without limitation the rights to
|
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
10
|
+
subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|