purus 0.0.3 → 0.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/README-ja.md CHANGED
@@ -1,6 +1,6 @@
1
1
  <div align="center">
2
2
 
3
- [![Logo](./logo.png)](https://purus.work)
3
+ [![Logo](https://raw.githubusercontent.com/otoneko1102/purus/refs/heads/main/logo.png)](https://purus.work)
4
4
 
5
5
  [English](https://raw.githubusercontent.com/otoneko1102/purus/refs/heads/main/README.md) | **日本語**
6
6
 
@@ -55,4 +55,4 @@ otoneko. https://github.com/otoneko1102
55
55
 
56
56
  ## ライセンス
57
57
 
58
- Apache 2.0 ライセンスに基づいて配布されます。詳細については、[LICENSE](./LICENSE) を参照してください。
58
+ Apache 2.0 ライセンスに基づいて配布されます。詳細については、[LICENSE](https://raw.githubusercontent.com/otoneko1102/purus/refs/heads/main/LICENSE) を参照してください。
package/README.md CHANGED
@@ -55,4 +55,4 @@ otoneko. https://github.com/otoneko1102
55
55
 
56
56
  ## License
57
57
 
58
- Distributed under the Apache 2.0 License. See [LICENSE](./LICENSE) for more information.
58
+ Distributed under the Apache 2.0 License. See [LICENSE](https://raw.githubusercontent.com/otoneko1102/purus/refs/heads/main/LICENSE) for more information.
package/bin/purus.js CHANGED
@@ -1,3 +1,52 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
- require("../lib/purus-compiler.js");
3
+
4
+ const VERSION = require("../package.json").version;
5
+ const cmd = process.argv[2];
6
+
7
+ function printHelp() {
8
+ console.log(`purus v${VERSION} - A language that compiles to JavaScript`);
9
+ console.log("");
10
+ console.log("Usage:");
11
+ console.log(" purus build [file] Compile to JavaScript");
12
+ console.log(" purus build --directory <dir> Compile all files in directory");
13
+ console.log(" purus build --output <dir> Specify output directory");
14
+ console.log(" purus build Compile using config.purus");
15
+ console.log(" .purus -> .js");
16
+ console.log(" .cpurus -> .cjs (CommonJS)");
17
+ console.log(" .mpurus -> .mjs (ES Module)");
18
+ console.log(" purus build --no-header [file] Compile without header comment");
19
+ console.log(" purus run [file] Run without generating files");
20
+ console.log(" purus run --directory <dir> Run all files in directory");
21
+ console.log(" purus run Run using config.purus");
22
+ console.log(" purus check <file> Syntax check only");
23
+ console.log(" purus new [name] [-y] Create a new project");
24
+ console.log(" purus init Initialize project in current directory");
25
+ console.log(" purus version Show version");
26
+ console.log(" purus help Show this help");
27
+ console.log("");
28
+ console.log("Aliases: compile = build, create = new");
29
+ }
30
+
31
+ switch (cmd) {
32
+ case "new":
33
+ case "create":
34
+ require("../lib/create.js");
35
+ break;
36
+ case "build":
37
+ case "compile":
38
+ require("../lib/build-wrapper.js");
39
+ break;
40
+ case "run":
41
+ require("../lib/run-wrapper.js");
42
+ break;
43
+ case "help":
44
+ case "--help":
45
+ case "-h":
46
+ case undefined:
47
+ printHelp();
48
+ break;
49
+ default:
50
+ require("../lib/purus-compiler.js");
51
+ break;
52
+ }
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const { loadConfig } = require("./config.js");
6
+ const { compile } = require("./purus-core.js");
7
+
8
+ const args = process.argv.slice(3);
9
+
10
+ let file = null;
11
+ let directory = null;
12
+ let output = null;
13
+ let noHeader = false;
14
+ let toStdout = false;
15
+
16
+ for (let i = 0; i < args.length; i++) {
17
+ if (args[i] === "--no-header") {
18
+ noHeader = true;
19
+ } else if (args[i] === "--stdout") {
20
+ toStdout = true;
21
+ } else if (args[i] === "--directory" || args[i] === "-d") {
22
+ directory = args[++i];
23
+ } else if (args[i] === "--output" || args[i] === "-o") {
24
+ output = args[++i];
25
+ } else if (!args[i].startsWith("-")) {
26
+ file = args[i];
27
+ }
28
+ }
29
+
30
+ if (file) {
31
+ // Single file - delegate to MoonBit compiler
32
+ require("./purus-compiler.js");
33
+ } else {
34
+ let entryDir;
35
+ let outputDir;
36
+ let useHeader;
37
+
38
+ if (directory) {
39
+ entryDir = path.resolve(directory);
40
+ outputDir = output ? path.resolve(output) : path.resolve("dist");
41
+ useHeader = !noHeader;
42
+
43
+ const result = loadConfig();
44
+ if (result) {
45
+ if (!output) {
46
+ outputDir = path.resolve(
47
+ result.configDir,
48
+ result.config.output || "dist"
49
+ );
50
+ }
51
+ useHeader = result.config.header !== false && !noHeader;
52
+ }
53
+ } else {
54
+ const result = loadConfig();
55
+ if (!result) {
56
+ console.log("Error: no input file specified and no config.purus found");
57
+ console.log("");
58
+ console.log("Usage:");
59
+ console.log(" purus build <file> Compile a single file");
60
+ console.log(
61
+ " purus build --directory <dir> Compile all files in directory"
62
+ );
63
+ console.log(
64
+ " purus build Compile using config.purus"
65
+ );
66
+ process.exit(1);
67
+ }
68
+
69
+ const { config, configDir } = result;
70
+ entryDir = path.resolve(configDir, config.entry || "src");
71
+ outputDir = output
72
+ ? path.resolve(output)
73
+ : path.resolve(configDir, config.output || "dist");
74
+ useHeader = config.header !== false && !noHeader;
75
+ }
76
+
77
+ if (!fs.existsSync(entryDir)) {
78
+ console.log(`Error: entry directory '${entryDir}' not found`);
79
+ process.exit(1);
80
+ }
81
+
82
+ const stat = fs.statSync(entryDir);
83
+ let files;
84
+
85
+ if (stat.isFile()) {
86
+ files = [entryDir];
87
+ // For single file entry, output is a file too
88
+ if (!fs.existsSync(path.dirname(outputDir))) {
89
+ fs.mkdirSync(path.dirname(outputDir), { recursive: true });
90
+ }
91
+ } else {
92
+ files = findPurusFiles(entryDir);
93
+ }
94
+
95
+ if (files.length === 0) {
96
+ console.log(`No .purus files found in ${entryDir}`);
97
+ process.exit(0);
98
+ }
99
+
100
+ let count = 0;
101
+ for (const f of files) {
102
+ const source = fs.readFileSync(f, "utf8");
103
+ const js = compile(source, { header: useHeader });
104
+ let outputPath;
105
+
106
+ if (stat.isFile()) {
107
+ outputPath = outputDir;
108
+ } else {
109
+ outputPath = getOutputPath(f, entryDir, outputDir);
110
+ }
111
+
112
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
113
+ fs.writeFileSync(outputPath, js);
114
+ console.log(
115
+ `Compiled ${path.relative(process.cwd(), f)} -> ${path.relative(process.cwd(), outputPath)}`
116
+ );
117
+ count++;
118
+ }
119
+ console.log(`\n${count} file${count === 1 ? "" : "s"} compiled.`);
120
+ }
121
+
122
+ function findPurusFiles(dir) {
123
+ const results = [];
124
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
125
+ const fullPath = path.join(dir, entry.name);
126
+ if (entry.isDirectory()) {
127
+ results.push(...findPurusFiles(fullPath));
128
+ } else if (/\.(c|m)?purus$/.test(entry.name)) {
129
+ results.push(fullPath);
130
+ }
131
+ }
132
+ return results;
133
+ }
134
+
135
+ function getOutputPath(inputFile, inputBase, outputBase) {
136
+ const relative = path.relative(inputBase, inputFile);
137
+ let ext = ".js";
138
+ if (inputFile.endsWith(".cpurus")) ext = ".cjs";
139
+ else if (inputFile.endsWith(".mpurus")) ext = ".mjs";
140
+ const base = relative.replace(/\.(c|m)?purus$/, "");
141
+ return path.join(outputBase, base + ext);
142
+ }
package/lib/config.js ADDED
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ function parseConfig(configPath) {
7
+ const content = fs.readFileSync(configPath, "utf8");
8
+ const config = {};
9
+
10
+ for (const line of content.split("\n")) {
11
+ const trimmed = line.trim();
12
+ if (trimmed === "" || trimmed.startsWith("--")) continue;
13
+
14
+ const match = trimmed.match(/^const\s+([\w.-]+)\s+be\s+(.+)$/);
15
+ if (!match) continue;
16
+
17
+ const key = match[1];
18
+ let value = match[2].trim();
19
+
20
+ if (value.startsWith("///") && value.endsWith("///")) {
21
+ value = value.slice(3, -3);
22
+ } else if (value === "true") {
23
+ value = true;
24
+ } else if (value === "false") {
25
+ value = false;
26
+ } else if (/^\d+$/.test(value)) {
27
+ value = parseInt(value, 10);
28
+ } else if (/^\d+\.\d+$/.test(value)) {
29
+ value = parseFloat(value);
30
+ }
31
+
32
+ const keys = key.split(".");
33
+ let target = config;
34
+ for (let i = 0; i < keys.length - 1; i++) {
35
+ if (!target[keys[i]] || typeof target[keys[i]] !== "object") {
36
+ target[keys[i]] = {};
37
+ }
38
+ target = target[keys[i]];
39
+ }
40
+ target[keys[keys.length - 1]] = value;
41
+ }
42
+
43
+ return config;
44
+ }
45
+
46
+ function findConfig(startDir) {
47
+ let dir = startDir || process.cwd();
48
+ while (true) {
49
+ const configPath = path.join(dir, "config.purus");
50
+ if (fs.existsSync(configPath)) return configPath;
51
+ const parent = path.dirname(dir);
52
+ if (parent === dir) return null;
53
+ dir = parent;
54
+ }
55
+ }
56
+
57
+ function loadConfig(startDir) {
58
+ const configPath = findConfig(startDir);
59
+ if (!configPath) return null;
60
+ return {
61
+ config: parseConfig(configPath),
62
+ configPath,
63
+ configDir: path.dirname(configPath),
64
+ };
65
+ }
66
+
67
+ module.exports = { parseConfig, findConfig, loadConfig };
package/lib/create.js ADDED
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const readline = require("readline");
6
+ const { spawnSync } = require("child_process");
7
+
8
+ function question(rl, text) {
9
+ return new Promise((resolve) => rl.question(text, (a) => resolve(a.trim())));
10
+ }
11
+
12
+ function isDirEmpty(dir) {
13
+ return fs.readdirSync(dir).length === 0;
14
+ }
15
+
16
+ async function run() {
17
+ const args = process.argv.slice(3);
18
+ const yesFlag = args.includes("-y") || args.includes("--yes");
19
+ const positionalArgs = args.filter((a) => !a.startsWith("-"));
20
+
21
+ let projectName = positionalArgs[0] || "";
22
+
23
+ const rl = readline.createInterface({
24
+ input: process.stdin,
25
+ output: process.stdout,
26
+ });
27
+
28
+ try {
29
+ if (!projectName) {
30
+ projectName = await question(rl, "Project name: ");
31
+ if (!projectName) {
32
+ console.log("Error: project name required");
33
+ process.exit(1);
34
+ }
35
+ }
36
+
37
+ const projectDir = path.resolve(projectName);
38
+
39
+ if (fs.existsSync(projectDir) && !isDirEmpty(projectDir)) {
40
+ console.log(`Error: directory '${projectName}' already exists and is not empty`);
41
+ process.exit(1);
42
+ }
43
+
44
+ console.log(`\nCreating project in ./${projectName}...`);
45
+ fs.mkdirSync(path.join(projectDir, "src"), { recursive: true });
46
+
47
+ // config.purus
48
+ const configPurus = `-- Purus Configuration
49
+
50
+ const entry be ///src///
51
+ const output be ///dist///
52
+ const header be true
53
+
54
+ -- Linter settings
55
+ const lint.no-var be ///warn///
56
+ const lint.indent-size be 2
57
+ const lint.max-line-length be ///off///
58
+ `;
59
+ fs.writeFileSync(path.join(projectDir, "config.purus"), configPurus);
60
+
61
+ // .prettierrc
62
+ const prettierrc =
63
+ JSON.stringify(
64
+ {
65
+ tabWidth: 2,
66
+ semi: false,
67
+ plugins: ["@puruslang/prettier-plugin-purus"],
68
+ },
69
+ null,
70
+ 2
71
+ ) + "\n";
72
+ fs.writeFileSync(path.join(projectDir, ".prettierrc"), prettierrc);
73
+
74
+ // src/main.purus
75
+ const mainPurus = `-- main.purus
76
+
77
+ const message be ///Hello, World///
78
+ console.log[message]
79
+ `;
80
+ fs.writeFileSync(path.join(projectDir, "src/main.purus"), mainPurus);
81
+
82
+ // README.md
83
+ const readme = `# ${projectName}
84
+
85
+ A [Purus](https://purus.work) project.
86
+
87
+ ## Getting Started
88
+
89
+ \`\`\`sh
90
+ npm install
91
+ purus build
92
+ \`\`\`
93
+
94
+ ## Scripts
95
+
96
+ | Script | Description |
97
+ |---|---|
98
+ | \`npm run build\` | Compile Purus to JavaScript |
99
+ | \`npm run exec\` | Run without compiling to files |
100
+ | \`npm run format\` | Format with Prettier |
101
+ | \`npm run lint\` | Lint with purus-lint |
102
+ `;
103
+ fs.writeFileSync(path.join(projectDir, "README.md"), readme);
104
+
105
+ // .gitignore
106
+ const gitignore = `dist/
107
+ node_modules/
108
+ `;
109
+ fs.writeFileSync(path.join(projectDir, ".gitignore"), gitignore);
110
+
111
+ console.log(" config.purus");
112
+ console.log(" .prettierrc");
113
+ console.log(" .gitignore");
114
+ console.log(" README.md");
115
+ console.log(" src/main.purus");
116
+
117
+ // npm init
118
+ console.log("");
119
+ if (yesFlag) {
120
+ console.log("Running npm init -y...");
121
+ spawnSync("npm", ["init", "-y"], {
122
+ cwd: projectDir,
123
+ stdio: "inherit",
124
+ shell: true,
125
+ });
126
+ } else {
127
+ console.log("Running npm init...");
128
+ spawnSync("npm", ["init"], {
129
+ cwd: projectDir,
130
+ stdio: "inherit",
131
+ shell: true,
132
+ });
133
+ }
134
+
135
+ // Add scripts to package.json
136
+ const pkgPath = path.join(projectDir, "package.json");
137
+ if (fs.existsSync(pkgPath)) {
138
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
139
+ pkg.scripts = {
140
+ ...(pkg.scripts || {}),
141
+ purus: "purus",
142
+ build: "purus build",
143
+ compile: "purus compile",
144
+ exec: "purus run",
145
+ format: "prettier --write ./src",
146
+ lint: "purus-lint",
147
+ };
148
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
149
+ console.log("\nAdded scripts to package.json");
150
+ }
151
+
152
+ // Ask about dependencies
153
+ let installDeps;
154
+ if (yesFlag) {
155
+ installDeps = true;
156
+ } else {
157
+ const answer = await question(rl, "\nInstall dependencies? (y/N) ");
158
+ installDeps =
159
+ answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
160
+ }
161
+
162
+ if (installDeps) {
163
+ const devDeps = [
164
+ "@puruslang/linter",
165
+ "@puruslang/prettier-plugin-purus",
166
+ "prettier",
167
+ "purus",
168
+ ];
169
+ console.log("\nInstalling devDependencies...");
170
+ for (const dep of devDeps) {
171
+ console.log(` ${dep}`);
172
+ }
173
+ console.log("");
174
+ spawnSync("npm", ["install", "--save-dev", ...devDeps], {
175
+ cwd: projectDir,
176
+ stdio: "inherit",
177
+ shell: true,
178
+ });
179
+ }
180
+
181
+ console.log("\nDone! To get started:");
182
+ console.log(` cd ${projectName}`);
183
+ if (!installDeps) {
184
+ console.log(" npm install");
185
+ }
186
+ console.log(" purus build");
187
+ } finally {
188
+ rl.close();
189
+ }
190
+ }
191
+
192
+ run().catch((err) => {
193
+ console.error(err.message);
194
+ process.exit(1);
195
+ });