Skip to content

Commit

Permalink
frogasaurus is now built with itself
Browse files Browse the repository at this point in the history
  • Loading branch information
TodePond committed Sep 4, 2022
1 parent c763abb commit e2681c2
Show file tree
Hide file tree
Showing 9 changed files with 668 additions and 299 deletions.
643 changes: 350 additions & 293 deletions frogasaurus.js

Large diffs are not rendered by default.

172 changes: 172 additions & 0 deletions source/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { capitalise } from "./string.js"
import { readDirectory, writeFile } from "./file.js"
import { parseExport, parseImport } from "./parse.js"

const HEADER_TITLE_LINES = [
`//=============//`,
`// FROGASAURUS //`,
`//=============//`,
``,
]
const SOURCE_TITLE_LINES = [
``,
`//========//`,
`// SOURCE //`,
`//========//`,
``,
]

const FOOTER_TITLE_LINES = [
``,
``,
`//=========//`,
`// EXPORTS //`,
`//=========//`,
``,
]

const MAIN_TITLE_LINES = [
``,
``,
`//======//`,
`// MAIN //`,
`//======//`,
``,
]

const HEADER_TITLE = HEADER_TITLE_LINES.join("\n")
const SOURCE_TITLE = SOURCE_TITLE_LINES.join("\n")
const FOOTER_TITLE = FOOTER_TITLE_LINES.join("\n")
const MAIN_TITLE = MAIN_TITLE_LINES.join("\n")

const transpileSource = (source, name, path, projectName) => {

const fileConstantName = `${capitalise(projectName)}Frogasaurus`
const lines = source.split("\n")

const strippedLines = []
const exportResults = []
const importResults = []

for (let i = 0; i < lines.length; i++) {
const line = lines[i]

const metadata = {fileName: name, lineNumber: i}
const exportResult = parseExport(line, metadata)
if (exportResult.success) {
strippedLines.push(`\t${exportResult.margin}${exportResult.tail}`)
exportResults.push(exportResult)
continue
}

const importResult = parseImport(line, metadata)
if (importResult.success) {
importResults.push(importResult)
continue
}

strippedLines.push(`\t${line}`)
}

const exportLines = []
for (const exportResult of exportResults) {
exportLines.push(`\t\t${fileConstantName}["${path}"].${exportResult.name} = ${exportResult.name}`)
}
const exportSource = exportLines.join("\n")
const innerSource = `\t\t${fileConstantName}["${path}"] = {}\n\t${strippedLines.join("\n\t")}\n\n${exportSource}`
const scopedSource = `\t//====== ${path} ======\n\t{\n${innerSource}\n\t}`

return {success: true, output: scopedSource, exportResults, importResults, path}
}

export const build = async (projectName) => {

console.clear()

const fileConstantName = `${capitalise(projectName)}Frogasaurus`

const entries = await readDirectory("source")
const sourceResults = entries.map(entry => transpileSource(entry.source, entry.name, entry.path, projectName))
if (sourceResults.some(result => !result.success)) {
console.log("%cFailed build", RED)
return
}

// Check for duplicate export names
const exportNames = new Set()
for (const result of sourceResults) {
for (const exportResult of result.exportResults) {
if (exportNames.has(exportResult.name)) {
console.log("%cSorry, you can't have multiple exports with the same name", RED)
console.log("%cThis is because Frogasaurus mashes all your exports together <3", RED)
console.log(`${result.path}`)
console.log(`\n\t${exportResult.name}\n`)
console.log("%cFailed build", RED)
return
}
exportNames.add(exportResult.name)
}
}

// Check for 'main' function export
let mainFuncDenoSource = ""
for (const result of sourceResults) {
for (const exportResult of result.exportResults) {
if (exportResult.name === "main") {
mainFuncDenoSource = `${MAIN_TITLE}${fileConstantName}["${result.path}"].main(...Deno.args)`
}
}
}

const exportFooterLines = sourceResults.map(result => `export const { ${result.exportResults.map(exportResult => exportResult.name).join(", ")} } = ${fileConstantName}["${result.path}"]`)
const exportFooterSource = exportFooterLines.join("\n")

const globalFooterLines = [
`const ${capitalise(projectName)} = {`,
]

for (const sourceResult of sourceResults) {
for (const exportResult of sourceResult.exportResults) {
globalFooterLines.push(`\t${exportResult.name}: ${fileConstantName}["${sourceResult.path}"].${exportResult.name},`)
}
}

globalFooterLines.push(`}`)
const globalFooterSource = globalFooterLines.join("\n")

const importLists = new Map()
for (const sourceResult of sourceResults) {

for (const importResult of sourceResult.importResults) {
if (importLists.get(importResult.path) === undefined) {
importLists.set(importResult.path, new Set())
}

const importList = importLists.get(importResult.path)
for (const name of importResult.names) {
importList.add(name)
}
}
}

const importFooterLines = []
for (const [path, importList] of importLists.entries()) {
importFooterLines.push(`\tconst { ${[...importList.values()].join(", ")} } = ${fileConstantName}["${path}"]`)
}
const importFooterSource = "\n\n" + importFooterLines.join("\n")

const transpiledSource = "{\n" + sourceResults.map(result => result.output).join("\n\n") + importFooterSource + "\n\n}"

const headerLine = `const ${fileConstantName} = {}\n`

const importSource = HEADER_TITLE + headerLine + SOURCE_TITLE + transpiledSource + FOOTER_TITLE + exportFooterSource + "\n\nexport " + globalFooterSource
const embedSource = HEADER_TITLE + headerLine + SOURCE_TITLE + transpiledSource + FOOTER_TITLE + globalFooterSource
const standaloneSource = HEADER_TITLE + headerLine + SOURCE_TITLE + transpiledSource + mainFuncDenoSource

await writeFile(`${projectName.toLowerCase()}-import.js`, importSource)
await writeFile(`${projectName.toLowerCase()}-embed.js`, embedSource)
await writeFile(`${projectName.toLowerCase()}-standalone.js`, standaloneSource)

console.log("%cFinished build!", YELLOW)
console.log("Waiting for file changes...")
}
4 changes: 4 additions & 0 deletions source/colour.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const BLUE = "color: rgb(0, 128, 255)"
export const RED = "color: rgb(255, 70, 70)"
export const GREEN = "color: rgb(0, 255, 128)"
export const YELLOW = "color: #ffcc46"
39 changes: 39 additions & 0 deletions source/file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { BLUE, RED, GREEN, YELLOW } from "./colour.js"

export const readFile = async (path) => {
console.log(`%cReading File: ${path}`, BLUE)
const source = await Deno.readTextFile(path)
return source
}

export const writeFile = async (path, source) => {
console.log(`%cWriting File: ${path}`, GREEN)
return await Deno.writeTextFile(path, source)
}

export const readDirectory = async (path) => {

const entries = []

for await (const entry of Deno.readDir(path)) {

const {name} = entry
const entryPath = `${path}/${name}`

// Go deeper if it's a directory
if (entry.isDirectory) {
entries.push(...await readDirectory(entryPath))
continue
}

// Make sure it's a javascript file
const [head, extension] = name.split(".")
if (extension !== "js") continue

const source = await readFile(entryPath)
entries.push({source, name, path: "./" + entryPath.slice("source/".length)})

}

return entries
}
1 change: 0 additions & 1 deletion source/greet.js

This file was deleted.

5 changes: 0 additions & 5 deletions source/hello.js

This file was deleted.

17 changes: 17 additions & 0 deletions source/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { build } from "./build.js"

export const main = async () => {
const directory = Deno.cwd()
const directoryParts = directory.split("\\")
const projectName = directoryParts[directoryParts.length-1]

await Deno.permissions.request({name: "read", path: "."})
await Deno.permissions.request({name: "write", path: "."})

await build(projectName)

const watcher = Deno.watchFs("./source")
for await (const event of watcher) {
await build(projectName)
}
}
71 changes: 71 additions & 0 deletions source/parse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { trimStart } from "./string.js"

export const getConstName = (line) => {
for (let i = "const ".length; i < line.length; i++) {
const char = line[i]
if (char === " " || char === " " || char === "=") {
return line.slice("const ".length, i)
}
}
}

export const getImportNames = (line) => {
const [head, tail] = line.split("{")
const [inner] = tail.split("}")
const names = inner.split(",").map(name => name.trim())
return names
}

export const getImportPath = (line) => {
const [head, tail] = line.split(" from ")
const [start, path, end] = tail.split(`"`)
return path
}

export const parseExport = (line, {fileName, lineNumber}) => {

const trim = trimStart(line)
const {trimmed, trimming} = trim
const exportSnippet = trimmed.slice(0, "export ".length)
if (exportSnippet !== "export ") return {success: false}

const trimLength = line.length - trimmed.length
const tail = line.slice("export ".length + trimLength)

const constSnippet = tail.slice(0, "const ".length)
if (constSnippet !== "const ") {
console.log(`%cError: Sorry, Frogasaurus only supports exports when you write 'const' immediately after.\n%c${fileName}:${lineNumber}\n\n ${line}\n`, RED, "")
return {success: false}
}

const name = getConstName(tail)
return {
success: true,
name,
margin: trimming,
tail,
}
}

export const parseImport = (line, {fileName, lineNumber}) => {
const trim = trimStart(line)
const {trimmed, trimming} = trim
const importSnippet = trimmed.slice(0, "import ".length)
if (importSnippet !== "import ") return {success: false}

const trimLength = line.length - trimmed.length
const tail = line.slice("import ".length + trimLength)

const path = getImportPath(tail)
const names = getImportNames(tail)
const output = `{ ${names.join(", ")} }`

return {
success: true,
names,
path,
output,
margin: trimming,
tail,
}
}
15 changes: 15 additions & 0 deletions source/string.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const trimStart = (string) => {
for (let i = 0; i < string.length; i++) {
const char = string[i]
if (char === " " || char === " ") continue
const trimmed = string.slice(i)
const trimming = string.slice(0, i)
return {trimmed, trimming}
}
return {trimmed: "", trimming: ""}
}

export const capitalise = (string) => {
const [head, ...tail] = string.split("")
return `${head.toUpperCase()}${tail.join("")}`
}

0 comments on commit e2681c2

Please sign in to comment.