scan2paperless/test.ts

402 lines
13 KiB
TypeScript

// deno-fmt-ignore-file
// deno-lint-ignore-file
// This code was bundled using `deno bundle` and it's not recommended to edit it manually
class DenoStdInternalError extends Error {
constructor(message){
super(message);
this.name = "DenoStdInternalError";
}
}
function assert(expr, msg = "") {
if (!expr) {
throw new DenoStdInternalError(msg);
}
}
const { hasOwn } = Object;
function get(obj, key) {
if (hasOwn(obj, key)) {
return obj[key];
}
}
function getForce(obj, key) {
const v = get(obj, key);
assert(v != null);
return v;
}
function isNumber(x) {
if (typeof x === "number") return true;
if (/^0x[0-9a-f]+$/i.test(String(x))) return true;
return /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(String(x));
}
function hasKey(obj, keys) {
let o = obj;
keys.slice(0, -1).forEach((key)=>{
o = get(o, key) ?? {};
});
const key = keys[keys.length - 1];
return hasOwn(o, key);
}
function parse(args, { "--": doubleDash = false , alias ={} , boolean: __boolean = false , default: defaults = {} , stopEarly =false , string =[] , collect =[] , negatable =[] , unknown =(i)=>i } = {}) {
const aliases = {};
const flags = {
bools: {},
strings: {},
unknownFn: unknown,
allBools: false,
collect: {},
negatable: {}
};
if (alias !== undefined) {
for(const key in alias){
const val = getForce(alias, key);
if (typeof val === "string") {
aliases[key] = [
val
];
} else {
aliases[key] = val;
}
for (const alias1 of getForce(aliases, key)){
aliases[alias1] = [
key
].concat(aliases[key].filter((y)=>alias1 !== y));
}
}
}
if (__boolean !== undefined) {
if (typeof __boolean === "boolean") {
flags.allBools = !!__boolean;
} else {
const booleanArgs = typeof __boolean === "string" ? [
__boolean
] : __boolean;
for (const key1 of booleanArgs.filter(Boolean)){
flags.bools[key1] = true;
const alias2 = get(aliases, key1);
if (alias2) {
for (const al of alias2){
flags.bools[al] = true;
}
}
}
}
}
if (string !== undefined) {
const stringArgs = typeof string === "string" ? [
string
] : string;
for (const key2 of stringArgs.filter(Boolean)){
flags.strings[key2] = true;
const alias3 = get(aliases, key2);
if (alias3) {
for (const al1 of alias3){
flags.strings[al1] = true;
}
}
}
}
if (collect !== undefined) {
const collectArgs = typeof collect === "string" ? [
collect
] : collect;
for (const key3 of collectArgs.filter(Boolean)){
flags.collect[key3] = true;
const alias4 = get(aliases, key3);
if (alias4) {
for (const al2 of alias4){
flags.collect[al2] = true;
}
}
}
}
if (negatable !== undefined) {
const negatableArgs = typeof negatable === "string" ? [
negatable
] : negatable;
for (const key4 of negatableArgs.filter(Boolean)){
flags.negatable[key4] = true;
const alias5 = get(aliases, key4);
if (alias5) {
for (const al3 of alias5){
flags.negatable[al3] = true;
}
}
}
}
const argv = {
_: []
};
function argDefined(key, arg) {
return flags.allBools && /^--[^=]+$/.test(arg) || get(flags.bools, key) || !!get(flags.strings, key) || !!get(aliases, key);
}
function setKey(obj, name, value, collect = true) {
let o = obj;
const keys = name.split(".");
keys.slice(0, -1).forEach(function(key) {
if (get(o, key) === undefined) {
o[key] = {};
}
o = get(o, key);
});
const key = keys[keys.length - 1];
const collectable = collect && !!get(flags.collect, name);
if (!collectable) {
o[key] = value;
} else if (get(o, key) === undefined) {
o[key] = [
value
];
} else if (Array.isArray(get(o, key))) {
o[key].push(value);
} else {
o[key] = [
get(o, key),
value
];
}
}
function setArg(key, val, arg = undefined, collect) {
if (arg && flags.unknownFn && !argDefined(key, arg)) {
if (flags.unknownFn(arg, key, val) === false) return;
}
const value = !get(flags.strings, key) && isNumber(val) ? Number(val) : val;
setKey(argv, key, value, collect);
const alias = get(aliases, key);
if (alias) {
for (const x of alias){
setKey(argv, x, value, collect);
}
}
}
function aliasIsBoolean(key) {
return getForce(aliases, key).some((x)=>typeof get(flags.bools, x) === "boolean");
}
let notFlags = [];
if (args.includes("--")) {
notFlags = args.slice(args.indexOf("--") + 1);
args = args.slice(0, args.indexOf("--"));
}
for(let i = 0; i < args.length; i++){
const arg = args[i];
if (/^--.+=/.test(arg)) {
const m = arg.match(/^--([^=]+)=(.*)$/s);
assert(m != null);
const [, key5, value] = m;
if (flags.bools[key5]) {
const booleanValue = value !== "false";
setArg(key5, booleanValue, arg);
} else {
setArg(key5, value, arg);
}
} else if (/^--no-.+/.test(arg) && get(flags.negatable, arg.replace(/^--no-/, ""))) {
const m1 = arg.match(/^--no-(.+)/);
assert(m1 != null);
setArg(m1[1], false, arg, false);
} else if (/^--.+/.test(arg)) {
const m2 = arg.match(/^--(.+)/);
assert(m2 != null);
const [, key6] = m2;
const next = args[i + 1];
if (next !== undefined && !/^-/.test(next) && !get(flags.bools, key6) && !flags.allBools && (get(aliases, key6) ? !aliasIsBoolean(key6) : true)) {
setArg(key6, next, arg);
i++;
} else if (/^(true|false)$/.test(next)) {
setArg(key6, next === "true", arg);
i++;
} else {
setArg(key6, get(flags.strings, key6) ? "" : true, arg);
}
} else if (/^-[^-]+/.test(arg)) {
const letters = arg.slice(1, -1).split("");
let broken = false;
for(let j = 0; j < letters.length; j++){
const next1 = arg.slice(j + 2);
if (next1 === "-") {
setArg(letters[j], next1, arg);
continue;
}
if (/[A-Za-z]/.test(letters[j]) && /=/.test(next1)) {
setArg(letters[j], next1.split(/=(.+)/)[1], arg);
broken = true;
break;
}
if (/[A-Za-z]/.test(letters[j]) && /-?\d+(\.\d*)?(e-?\d+)?$/.test(next1)) {
setArg(letters[j], next1, arg);
broken = true;
break;
}
if (letters[j + 1] && letters[j + 1].match(/\W/)) {
setArg(letters[j], arg.slice(j + 2), arg);
broken = true;
break;
} else {
setArg(letters[j], get(flags.strings, letters[j]) ? "" : true, arg);
}
}
const [key7] = arg.slice(-1);
if (!broken && key7 !== "-") {
if (args[i + 1] && !/^(-|--)[^-]/.test(args[i + 1]) && !get(flags.bools, key7) && (get(aliases, key7) ? !aliasIsBoolean(key7) : true)) {
setArg(key7, args[i + 1], arg);
i++;
} else if (args[i + 1] && /^(true|false)$/.test(args[i + 1])) {
setArg(key7, args[i + 1] === "true", arg);
i++;
} else {
setArg(key7, get(flags.strings, key7) ? "" : true, arg);
}
}
} else {
if (!flags.unknownFn || flags.unknownFn(arg) !== false) {
argv._.push(flags.strings["_"] ?? !isNumber(arg) ? arg : Number(arg));
}
if (stopEarly) {
argv._.push(...args.slice(i + 1));
break;
}
}
}
for (const [key8, value1] of Object.entries(defaults)){
if (!hasKey(argv, key8.split("."))) {
setKey(argv, key8, value1);
if (aliases[key8]) {
for (const x of aliases[key8]){
setKey(argv, x, value1);
}
}
}
}
for (const key9 of Object.keys(flags.bools)){
if (!hasKey(argv, key9.split("."))) {
const value2 = get(flags.collect, key9) ? [] : false;
setKey(argv, key9, value2, false);
}
}
for (const key10 of Object.keys(flags.strings)){
if (!hasKey(argv, key10.split(".")) && get(flags.collect, key10)) {
setKey(argv, key10, [], false);
}
}
if (doubleDash) {
argv["--"] = [];
for (const key11 of notFlags){
argv["--"].push(key11);
}
} else {
for (const key12 of notFlags){
argv._.push(key12);
}
}
return argv;
}
const DEFAULT_BUFFER_SIZE = 32 * 1024;
async function* iterateReader(r, options) {
const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE;
const b = new Uint8Array(bufSize);
while(true){
const result = await r.read(b);
if (result === null) {
break;
}
yield b.slice(0, result);
}
}
const getRandomString = ()=>`${+new Date()}-${Math.round(Math.random() * 100000)}`;
(async ()=>{
const tempDir = await Deno.makeTempDir();
const args = parse(Deno.args);
if (args.help || args.h) {
console.log(`
Usage: scan2paperless [OPTION] [NAME]
Scan one or multiple pages into a PDF document ready for consumption by paperless
NAME will be the name of the document without extension.
Available options:
-d | --device=DEVICE Scanner device to use. Check \`scanimage -L\` for available devices.
This defaults to \$SCANNER_DEFAULT_DEVICE
-o | --output-dir=DIR Directory to write final PDF to. This defaults to \$SCANNER_OUTPUT_DIR
-h | --help show this help message
Example:
scan2paperless \
-d hp3900:libusb:005:002 \
-n 5 \
-o /var/lib/paperless/consume \
bank-statement-01-2023
`);
Deno.exit(0);
}
const device = args.d || args.device || Deno.env.get('SCANNER_DEFAULT_DEVICE');
const outputDir = args.d || args.device || Deno.env.get('SCANNER_OUTPUT_DIR');
const outputName = args._[0] || getRandomString();
const files = [];
let cancelled = false;
do {
const fileName = `${tempDir}/${getRandomString()}.jpg`;
const scanimageProcess = Deno.run({
cmd: [
"scanimage",
"-d",
device,
"--format=jpeg",
"--resolution",
"300",
"--progress",
"-o",
fileName,
],
stderr: 'piped'
});
const decoder = new TextDecoder();
const encoder = new TextEncoder();
let inScan = false;
for await (const chunk of iterateReader(scanimageProcess.stderr)){
const line = decoder.decode(chunk);
if (!inScan && line.startsWith('Progress: ')) {
inScan = true;
Deno.kill(scanimageProcess.pid, 'SIGSTOP');
const answer = prompt("Scan next page? [Y/n]");
if (answer && ![
'y',
'yes'
].includes(answer.toLowerCase())) {
Deno.kill(scanimageProcess.pid, 'SIGKILL');
cancelled = true;
break;
} else {
Deno.kill(scanimageProcess.pid, 'SIGCONT');
cancelled = false;
}
}
Deno.stdout.write(encoder.encode(`\r${line}`));
}
const status = await scanimageProcess.status();
scanimageProcess.stderr.close();
if (!cancelled && status.code !== 0) {
console.error('Scan seems to have failed with status ', status.code);
}
if (!cancelled) {
files.push(fileName);
}
}while (!cancelled)
const pdfFile = `${outputDir}/${outputName}.pdf`;
const img2pdfProcess = Deno.run({
cmd: [
"img2pdf",
"--output",
pdfFile,
...files,
]
});
const pdfStatus = await img2pdfProcess.status();
if (pdfStatus.code !== 0) {
console.error('Error writing PDF');
Deno.exit(2);
}
console.log(`PDF written to ${pdfFile}`);
})();