Initial commit :P

This commit is contained in:
mmcwilliams 2024-01-06 00:25:07 -05:00
commit 815538cda6
29 changed files with 7496 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules
.env
www
*.DS_Store

7
LICENSE Normal file
View File

@ -0,0 +1,7 @@
Copyright 2024 Matthew McWilliams
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# photosite
Static site generator for building a simple photo site.

4
default.env Normal file
View File

@ -0,0 +1,4 @@
INBOX="~/Photos/toprocess"
PHOTOS="~/Photos/photosite"
WWW="./www"
TEMPLATES="./views"

11
dist/build.js vendored Normal file
View File

@ -0,0 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const log_1 = require("./log");
class Build {
constructor() {
this.log = (0, log_1.createLog)('build');
this.log.info(`Building site: ${new Date()}`);
}
}
new Build();
//# sourceMappingURL=build.js.map

1
dist/build.js.map vendored Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":";;AAAA,+BAAkC;AAGlC,MAAM,KAAK;IAEV;QACC,IAAI,CAAC,GAAG,GAAG,IAAA,eAAS,EAAC,OAAO,CAAC,CAAC;QAC9B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,kBAAkB,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC;IAC/C,CAAC;CACD;AAED,IAAI,KAAK,EAAE,CAAC"}

50
dist/log/index.js vendored Normal file
View File

@ -0,0 +1,50 @@
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.createLog = void 0;
/** @module log */
/** Wrapper for winston that tags streams and optionally writes files with a simple interface. */
/** Module now also supports optional papertrail integration, other services to follow */
const winston_1 = require("winston");
const { SPLAT } = require('triple-beam');
const { isObject } = require('lodash');
const APP_NAME = process.env.APP_NAME || 'default';
let winstonPapertrail;
function formatObject(param) {
if (isObject(param)) {
return JSON.stringify(param);
}
return param;
}
const all = (0, winston_1.format)((info) => {
const splat = info[SPLAT] || [];
const message = formatObject(info.message);
const rest = splat.map(formatObject).join(' ');
info.message = `${message} ${rest}`;
return info;
});
const myFormat = winston_1.format.printf(({ level, message, label, timestamp }) => {
return `${timestamp} [${label}] ${level}: ${message}`;
});
/**
* Returns a winston logger configured to service
*
* @param {string} label Label appearing on logger
* @param {string} filename Optional file to write log to
*
* @returns {object} Winston logger
*/
function createLog(label, filename = null) {
const tports = [new (winston_1.transports.Console)()];
const fmat = winston_1.format.combine(all(), winston_1.format.label({ label }), winston_1.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }), winston_1.format.colorize(), myFormat);
let papertrailOpts;
if (filename !== null) {
tports.push(new (winston_1.transports.File)({ filename }));
}
return (0, winston_1.createLogger)({
format: fmat,
transports: tports
});
}
exports.createLog = createLog;
module.exports = { createLog };
//# sourceMappingURL=index.js.map

1
dist/log/index.js.map vendored Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/log/index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;;;AAEZ,kBAAkB;AAClB,iGAAiG;AACjG,yFAAyF;AAEzF,qCAA2D;AAC3D,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;AACzC,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEvC,MAAM,QAAQ,GAAY,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,SAAS,CAAC;AAE5D,IAAI,iBAAiB,CAAC;AAEtB,SAAS,YAAY,CAAE,KAAW;IAChC,IAAI,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACpB,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,GAAG,GAAG,IAAA,gBAAM,EAAC,CAAC,IAAU,EAAE,EAAE;IAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IAChC,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3C,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/C,IAAI,CAAC,OAAO,GAAG,GAAG,OAAO,IAAI,IAAI,EAAE,CAAC;IACpC,OAAO,IAAI,CAAC;AAChB,CAAC,CAAC,CAAC;AAEH,MAAM,QAAQ,GAAG,gBAAM,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAQ,EAAE,EAAE;IAC5E,OAAO,GAAG,SAAS,KAAK,KAAK,KAAK,KAAK,KAAK,OAAO,EAAE,CAAC;AACxD,CAAC,CAAC,CAAC;AAEH;;;;;;;EAOE;AACF,SAAgB,SAAS,CAAE,KAAc,EAAE,WAAoB,IAAI;IAC/D,MAAM,MAAM,GAAW,CAAE,IAAI,CAAC,oBAAU,CAAC,OAAO,CAAC,EAAE,CAAE,CAAC;IACtD,MAAM,IAAI,GAAS,gBAAM,CAAC,OAAO,CAC7B,GAAG,EAAE,EACL,gBAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,EACvB,gBAAM,CAAC,SAAS,CAAC,EAAC,MAAM,EAAE,yBAAyB,EAAC,CAAC,EACrD,gBAAM,CAAC,QAAQ,EAAE,EACjB,QAAQ,CACX,CAAC;IACF,IAAI,cAAoB,CAAC;IAEzB,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,CAAC,IAAI,CAAE,IAAI,CAAC,oBAAU,CAAC,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAE,CAAC;IACvD,CAAC;IAED,OAAO,IAAA,sBAAY,EAAC;QAChB,MAAM,EAAG,IAAI;QACb,UAAU,EAAG,MAAM;KACtB,CAAC,CAAC;AACP,CAAC;AAnBD,8BAmBC;AAED,MAAM,CAAC,OAAO,GAAG,EAAE,SAAS,EAAE,CAAC"}

66
dist/shell/index.js vendored Normal file
View File

@ -0,0 +1,66 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Shell = void 0;
const child_process_1 = require("child_process");
const log_1 = require("../log");
const os_1 = require("os");
class Shell {
constructor(args, stdio = null, stderr = null, after = null, silent = false) {
this.lines = [];
this.stdio = null;
this.stderr = null;
this.after = null;
this.silent = false;
const bin = args.shift();
this.bin = bin;
this.args = args;
this.stdio = stdio;
this.stderr = stderr;
this.silent = silent;
this.after = after;
this.log = (0, log_1.createLog)(bin);
}
async execute() {
return new Promise((resolve, reject) => {
this.child = (0, child_process_1.spawn)(this.bin, this.args);
this.log.info(`Shell: ${this.bin} ${this.args.join(' ')}`);
this.child.stdout.on('data', (data) => {
if (!this.silent)
this.log.info(data);
if (this.after !== null)
this.lines.push(data);
if (this.stdio !== null) {
this.stdio(data);
}
});
this.child.stderr.on('data', (data) => {
if (!this.silent)
this.log.warn(data);
if (this.stderr !== null) {
this.stderr(data);
}
});
this.child.on('close', (code) => {
if (this.after !== null) {
this.after(this.lines.join(os_1.EOL));
}
if (code === 0) {
this.log.info(`Complete: ${this.bin} ${this.args.join(' ')}`);
return resolve(code);
}
else {
this.log.error(`Error executing: ${this.bin} ${this.args.join(' ')}`);
return reject(code);
}
});
});
}
kill() {
this.log.warn(`Killing: ${this.bin} ${this.args.join(' ')}`);
//this.child.stdin.pause();
this.child.kill();
}
}
exports.Shell = Shell;
module.exports = { Shell };
//# sourceMappingURL=index.js.map

1
dist/shell/index.js.map vendored Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/shell/index.ts"],"names":[],"mappings":"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"}

74
dist/templates/index.js vendored Normal file
View File

@ -0,0 +1,74 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Templates = void 0;
const handlebars_1 = __importDefault(require("handlebars"));
const handlebars_helpers_1 = __importDefault(require("handlebars-helpers"));
const promises_1 = require("fs/promises");
const path_1 = require("path");
const log_1 = require("../log");
(0, handlebars_helpers_1.default)();
class Templates {
constructor(dir = './views') {
this.templates = {};
this.dir = dir;
this.log = (0, log_1.createLog)('tmpl');
}
async build() {
const partialsPath = (0, path_1.join)(this.dir, 'partials');
let partials = [];
let templates = [];
let text;
let name;
try {
partials = await (0, promises_1.readdir)(partialsPath);
}
catch (err) {
this.log.error(err);
}
partials = partials.map(el => (0, path_1.join)(partialsPath, el));
for (let partial of partials) {
name = (0, path_1.parse)((0, path_1.basename)(partial)).name;
try {
text = await (0, promises_1.readFile)(partial, 'utf8');
}
catch (err) {
this.log.error(err);
continue;
}
handlebars_1.default.registerPartial(name, text);
this.log.info(`[partial] ${name}`);
}
try {
templates = await (0, promises_1.readdir)(this.dir);
}
catch (err) {
this.log.error(err);
}
templates = templates
.filter(el => el.indexOf('.hbs') !== -1 || el.indexOf('.handlebars') !== -1)
.map(el => (0, path_1.join)(this.dir, el));
for (let template of templates) {
name = (0, path_1.parse)((0, path_1.basename)(template)).name;
try {
text = await (0, promises_1.readFile)(template, 'utf8');
}
catch (err) {
this.log.error(err);
continue;
}
this.templates[name] = handlebars_1.default.compile(text);
this.log.info(`[template] ${name}`);
}
}
render(name, data) {
const keys = Object.keys(data);
this.log.info(`[render] ${name} with keys { ${keys.join(', ')} }`);
return this.templates[name](data);
}
}
exports.Templates = Templates;
module.exports = { Templates };
//# sourceMappingURL=index.js.map

1
dist/templates/index.js.map vendored Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"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"}

6920
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

39
package.json Normal file
View File

@ -0,0 +1,39 @@
{
"name": "photosite",
"version": "0.0.1",
"description": "Static site generator for processing photos.",
"main": "dist/index.js",
"scripts": {
"test": "node test/",
"compile": "./node_modules/.bin/tsc -p tsconfig.json",
"build" : "bash scripts/build.sh",
"generate" : "bash scripts/generate.sh",
"all" : "bash scripts/generate.sh"
},
"repository": {
"type": "git",
"url": "https://git.sixteenmillimeter.com/mattmcw/photosite.git"
},
"author": "Matthew McWilliams",
"license": "MIT",
"devDependencies": {
"@types/handlebars-helpers": "^0.5.6",
"@types/lodash": "^4.14.202",
"@types/node": "^20.10.6",
"@types/sqlite3": "^3.1.11",
"@types/triple-beam": "^1.3.5",
"@types/winston": "^2.4.4",
"typescript": "^5.3.3"
},
"dependencies": {
"dotenv": "^16.3.1",
"handlebars": "^4.7.8",
"handlebars-helpers": "^0.10.0",
"lodash": "^4.17.21",
"mime": "^4.0.1",
"sqlite3": "^5.1.7",
"triple-beam": "^1.4.1",
"uuid": "^9.0.1",
"winston": "^3.11.0"
}
}

6
scripts/all.sh Normal file
View File

@ -0,0 +1,6 @@
#!/bin/bash
set -e
node dist/generate
node dist/build

5
scripts/build.sh Normal file
View File

@ -0,0 +1,5 @@
#!/bin/bash
set -e
node dist/build

5
scripts/generate.sh Normal file
View File

@ -0,0 +1,5 @@
#!/bin/bash
set -e
node dist/generate

8
sql/setup.sql Normal file
View File

@ -0,0 +1,8 @@
CREATE TABLE IF NOT EXISTS photos {
}
CREATE TABLE IF NOT EXISTS version {
id TEXT PRIMARY KEY,
updated INTEGER UNIQUE
}

14
src/build.ts Normal file
View File

@ -0,0 +1,14 @@
import { createLog } from './log';
import type { Logger } from 'winston';
import { Templates } from './templates';
import { Database } from 'sqlite3'
class Build {
private log : Logger;
constructor () {
this.log = createLog('build');
this.log.info(`Building site: ${new Date()}`);
}
}
new Build();

14
src/generate.ts Normal file
View File

@ -0,0 +1,14 @@
import { createLog } from './log';
import type { Logger } from 'winston';
import { Shell } from './shell';
import { Database } from 'sqlite3';
class Generate {
private log : Logger;
constructor () {
this.log = createLog('generate');
this.log.info(`Generating site: ${new Date()}`);
}
}
new Generate();

63
src/log/index.ts Normal file
View File

@ -0,0 +1,63 @@
'use strict'
/** @module log */
/** Wrapper for winston that tags streams and optionally writes files with a simple interface. */
/** Module now also supports optional papertrail integration, other services to follow */
import { format, transports, createLogger } from 'winston';
const { SPLAT } = require('triple-beam');
const { isObject } = require('lodash');
const APP_NAME : string = process.env.APP_NAME || 'default';
let winstonPapertrail;
function formatObject (param : any) {
if (isObject(param)) {
return JSON.stringify(param);
}
return param;
}
const all = format((info : any) => {
const splat = info[SPLAT] || [];
const message = formatObject(info.message);
const rest = splat.map(formatObject).join(' ');
info.message = `${message} ${rest}`;
return info;
});
const myFormat = format.printf(({ level, message, label, timestamp } : any) => {
return `${timestamp} [${label}] ${level}: ${message}`;
});
/**
* Returns a winston logger configured to service
*
* @param {string} label Label appearing on logger
* @param {string} filename Optional file to write log to
*
* @returns {object} Winston logger
*/
export function createLog (label : string, filename : string = null) {
const tports : any[] = [ new (transports.Console)() ];
const fmat : any = format.combine(
all(),
format.label({ label }),
format.timestamp({format: 'YYYY-MM-DD HH:mm:ss.SSS'}),
format.colorize(),
myFormat,
);
let papertrailOpts : any;
if (filename !== null) {
tports.push( new (transports.File)({ filename }) );
}
return createLogger({
format : fmat,
transports : tports
});
}
module.exports = { createLog };

71
src/shell/index.ts Normal file
View File

@ -0,0 +1,71 @@
import { spawn, ChildProcessWithoutNullStreams } from 'child_process';
import { createLog } from '../log';
import type { Logger } from 'winston';
import { EOL } from 'os';
export class Shell {
private child : ChildProcessWithoutNullStreams;
private log : Logger;
private bin : string;
private args : any[];
private lines : string[] = [];
private stdio : Function = null;
private stderr : Function = null;
private after : Function = null;
private silent : boolean = false;
constructor (args : any[], stdio : Function = null, stderr : Function = null, after : Function = null, silent : boolean = false) {
const bin : string = args.shift();
this.bin = bin;
this.args = args;
this.stdio = stdio;
this.stderr = stderr;
this.silent = silent;
this.after = after;
this.log = createLog(bin);
}
public async execute () : Promise<number> {
return new Promise((resolve : Function, reject : Function) => {
this.child = spawn(this.bin, this.args);
this.log.info(`Shell: ${this.bin} ${this.args.join(' ')}`);
this.child.stdout.on('data', (data : string) => {
if (!this.silent) this.log.info(data);
if (this.after !== null) this.lines.push(data);
if (this.stdio !== null) {
this.stdio(data);
}
});
this.child.stderr.on('data', (data : string) => {
if (!this.silent) this.log.warn(data);
if (this.stderr !== null) {
this.stderr(data);
}
});
this.child.on('close', (code : number) => {
if (this.after !== null) {
this.after(this.lines.join(EOL));
}
if (code === 0) {
this.log.info(`Complete: ${this.bin} ${this.args.join(' ')}`);
return resolve(code);
} else {
this.log.error(`Error executing: ${this.bin} ${this.args.join(' ')}`);
return reject(code);
}
});
});
}
public kill () {
this.log.warn(`Killing: ${this.bin} ${this.args.join(' ')}`);
//this.child.stdin.pause();
this.child.kill();
}
}
module.exports = { Shell };

78
src/templates/index.ts Normal file
View File

@ -0,0 +1,78 @@
import Handlebars from 'handlebars';
import helpers from 'handlebars-helpers';
import { readFile, readdir } from 'fs/promises';
import { join, parse, basename } from 'path';
import { createLog } from '../log';
import type { Logger } from 'winston';
helpers();
export class Templates {
private log : Logger;
private dir : string;
private templates : any = {};
constructor (dir : string = './views') {
this.dir = dir;
this.log = createLog('tmpl');
}
public async build () {
const partialsPath : string = join(this.dir, 'partials');
let partials : string[] = [];
let templates : string[] = [];
let text : string;
let name : string;
try {
partials = await readdir(partialsPath);
} catch (err) {
this.log.error(err);
}
partials = partials.map(el => join(partialsPath, el));
for (let partial of partials) {
name = parse(basename(partial)).name;
try {
text = await readFile(partial, 'utf8');
} catch (err) {
this.log.error(err);
continue;
}
Handlebars.registerPartial(name, text);
this.log.info(`[partial] ${name}`);
}
try {
templates = await readdir(this.dir);
} catch (err) {
this.log.error(err);
}
templates = templates
.filter(el => el.indexOf('.hbs') !== -1 || el.indexOf('.handlebars') !== -1)
.map(el => join(this.dir, el));
for (let template of templates) {
name = parse(basename(template)).name;
try {
text = await readFile(template, 'utf8');
} catch (err) {
this.log.error(err);
continue;
}
this.templates[name] = Handlebars.compile(text);
this.log.info(`[template] ${name}`);
}
}
public render (name : string, data : any) : string {
const keys : string[] = Object.keys(data);
this.log.info(`[render] ${name} with keys { ${keys.join(', ')} }`);
return this.templates[name](data);
}
}
module.exports = { Templates };

1
test/index.js Normal file
View File

@ -0,0 +1 @@
require('./templates')

14
test/templates.js Normal file
View File

@ -0,0 +1,14 @@
const { createLog } = require('../dist/log');
const { Templates } = require('../dist/templates');
const log = createLog('tmpl-test');
(async function main () {
const tmpl = new Templates('./views');
try {
await tmpl.build();
} catch (err) {
log.error(err);
}
log.info(tmpl.render('index', { body : 'test' }));
})()

19
tsconfig.json Normal file
View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"target": "ES2020",
"noImplicitAny": true,
"moduleResolution": "node",
"sourceMap": true,
"removeComments" : false,
"baseUrl" : "dist",
"outDir": "./dist/",
"rootDir" : "./src/",
"paths" : {
}
},
"exclude" : [
"./dist"
]
}

3
views/index.hbs Normal file
View File

@ -0,0 +1,3 @@
{{> head}}
{{{body}}}
{{> foot}}

5
views/partials/foot.hbs Normal file
View File

@ -0,0 +1,5 @@
<script src="/js/script.js"></script>
<script>
</script>
</body>
</html>

8
views/partials/head.hbs Normal file
View File

@ -0,0 +1,8 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{{title}}</title>
<link href="/css/style.css" rel="stylesheet" />
</head>
<body>