Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions res/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ <h2>Mod name</h2>
<label for="mod-name-input" class="property-description">The human-readable name of your mod.</label>
<input type="text" id="mod-name-input">

<h2>Mod ID (optional)</h2>
<label for="mod-id-input" id="mod-id-label" class="property-description">A unique ID for your mod.</label>
<input type="text" id="mod-id-input">

<h2>Package name</h2>
<label for="package-input" class="property-description">A unique package name for your mod.</label>
<input type="text" id="package-input">
Expand Down Expand Up @@ -117,6 +113,25 @@ <h2>Dependencies</h2>
</div>
</div>

<div class="optional-panel">
<fieldset>
<legend>Optional Properties</legend>

<div class="optional-grid">
<div>
<h2>Mod ID</h2>
<label for="mod-id-input" id="mod-id-label" class="property-description">A unique ID for your mod.</label>
<input type="text" id="mod-id-input">
</div>
<div>
<h2>Main class name</h2>
<label for="main-class-name-input" class="property-description">The name for your initialisation classes.</label>
<input type="text" id="main-class-name-input">
</div>
</div>
</fieldset>
</div>

<script type="module" src="script.js"></script>
</body>

Expand Down
39 changes: 35 additions & 4 deletions res/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

import init, {
arch_api_supports_forge,
create_state,
generate,
is_valid_main_class_name,
is_valid_mod_id,
list_all_minecraft_versions,
sanitize_class_name,
supports_forge,
supports_neoforge,
arch_api_supports_forge,
to_mod_id,
validate_java_class_name,
validate_mod_id
} from "./templateer.js";
await init();
Expand Down Expand Up @@ -53,14 +56,17 @@ refreshFabricLikeCheckbox();
// Add generated mod id placeholder when not specified manually
const modNameInput = document.getElementById("mod-name-input");
const modIdInput = document.getElementById("mod-id-input");
const mainClassNameInput = document.getElementById("main-class-name-input");

modNameInput.oninput = () => {
refreshModIdPlaceholder();
refreshPlaceholders();
validateModId();
validateMainClassName();
};

function refreshModIdPlaceholder() {
function refreshPlaceholders() {
modIdInput.placeholder = to_mod_id(modNameInput.value) ?? "";
mainClassNameInput.placeholder = sanitize_class_name(modNameInput.value);
}

// Validate mod ids
Expand Down Expand Up @@ -89,6 +95,26 @@ function getModId() {
return value;
}

const mainClassNameLabel = document.querySelector("label[for='main-class-name-input']");
mainClassNameInput.oninput = validateMainClassName;

function validateMainClassName() {
const [ok, msg] = validate_java_class_name(getMainClassName());
if (ok) {
mainClassNameLabel.removeAttribute("error");
} else {
mainClassNameLabel.setAttribute("error", msg);
}
}

function isMainClassNameValid() {
return is_valid_main_class_name(getMainClassName());
}

function getMainClassName() {
return mainClassNameInput.value || mainClassNameInput.placeholder;
}

function getProjectType() {
for (const input of projectTypeToggles) {
if (input.checked) {
Expand All @@ -108,6 +134,7 @@ function getMappingSet() {

function updateState() {
state.mod_name = modNameInput.value;
state.main_class_name = getMainClassName();
state.mod_id = getModId();
state.package_name = document.getElementById("package-input").value;
state.game_version = mcSelect.value;
Expand Down Expand Up @@ -214,6 +241,9 @@ document.getElementById("generate-button").onclick = async () => {
} else if (!isModIdValid()) {
showError("Mod ID is not valid");
return;
} else if (!isMainClassNameValid()) {
showError("Main class name is not valid");
return;
} else if (state.package_name === "") {
showError("Package name is empty");
return;
Expand All @@ -229,7 +259,8 @@ document.getElementById("generate-button").onclick = async () => {
// Apply initial state
modNameInput.value = state.mod_name;
modIdInput.value = state.mod_id;
refreshModIdPlaceholder();
mainClassNameInput.value = state.main_class_name;
refreshPlaceholders();
refreshAvailablePlatforms();
document.getElementById("package-input").value = state.package_name;
document.getElementById("architectury-api-input").checked = state.dependencies.architectury_api;
11 changes: 11 additions & 0 deletions res/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,17 @@ h1 {
justify-content: center;
}

.optional-panel {
max-width: 55em;
margin: auto;
}

.optional-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
margin-top: -1.5rem;
}

.toggle-button {
display: inline-block;
margin: 0.5ex;
Expand Down
2 changes: 2 additions & 0 deletions src/app/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ pub async fn generate(app: &super::GeneratorApp, version_list: &MinecraftVersion
context.put("MOD_ID", mod_id);
let escaped_name = escape_json_and_toml(&app.mod_name);
context.put("MOD_NAME", escaped_name);
let escaped_main_class_name = escape_json_and_toml(&app.main_class_name);
context.put("MAIN_CLASS_NAME", escaped_main_class_name);

// Game version-specific
let game_version = version_list.versions.iter()
Expand Down
6 changes: 4 additions & 2 deletions src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,26 +66,28 @@ impl Default for Dependencies {
#[derive(Serialize, Deserialize)]
pub struct GeneratorApp {
pub mod_name: String,
pub mod_id: String,
pub package_name: String,
pub game_version: String,
pub project_type: ProjectType,
pub subprojects: Subprojects,
pub mapping_set: MappingSet,
pub dependencies: Dependencies,
pub mod_id: String,
pub main_class_name: String,
}

impl GeneratorApp {
pub fn new(list: &MinecraftVersionList) -> Self {
Self {
mod_name: "Example Mod".to_owned(),
mod_id: String::new(),
package_name: "com.example".to_owned(),
game_version: list.latest_version.clone(),
project_type: Default::default(),
subprojects: Default::default(),
mapping_set: Default::default(),
dependencies: Default::default(),
mod_id: String::new(),
main_class_name: String::new(),
}
}

Expand Down
59 changes: 59 additions & 0 deletions src/class_names.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use crate::{err, Result};

pub fn sanitize_class_name(s: &str) -> String {
let mut result = String::with_capacity(s.len());
let mut prev_allowed = false;
let mut started = false;

for c in s.trim().chars() {
if !(c.is_alphanumeric() || c == '_') {
prev_allowed = false;
continue;
}

if !started {
if c.is_ascii_alphabetic() {
result.push(c.to_ascii_uppercase());
started = true;
prev_allowed = true;
}
continue;
}

let c = if !prev_allowed && c.is_ascii_alphabetic() {
c.to_ascii_uppercase()
} else {
c
};

result.push(c);
prev_allowed = true;
}

result
}

pub fn validate_name<S: AsRef<str>>(name: S) -> Result<()> {
let name = name.as_ref();

if name.is_empty() {
return Err(err!("Name cannot be empty"));
}

let mut chars = name.chars();
if !chars.next().unwrap().is_ascii_alphabetic() {
return Err(err!("Java class name must start with a letter"));
}

for c in chars {
if !(c.is_ascii_alphanumeric() || c == '_') {
return Err(err!("'{}' is not valid in a Java class name", c));
}
}

Ok(())
}

pub fn is_valid_main_class_name<S: AsRef<str>>(id: S) -> bool {
validate_name(id).is_ok()
}
20 changes: 18 additions & 2 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ fn prompt(default_name: Option<&str>, version_list: &MinecraftVersionList) -> Re
.validate_interactively(ModIdValidate)
.interact()?;

let main_class_name: String = input("Main class name")
.default_input(&crate::class_names::sanitize_class_name(&mod_name))
.validate_interactively(JavaClassNameValidate)
.interact()?;

let package_name: String = input("Package name")
.interact()?;

Expand Down Expand Up @@ -179,13 +184,14 @@ fn prompt(default_name: Option<&str>, version_list: &MinecraftVersionList) -> Re

let generator = GeneratorApp {
mod_name,
mod_id,
package_name,
game_version: game_version.version.clone(),
project_type,
subprojects,
mapping_set,
dependencies
dependencies,
mod_id,
main_class_name
};
Ok(generator)
}
Expand All @@ -206,6 +212,16 @@ impl cliclack::Validate<String> for ModIdValidate {
}
}

struct JavaClassNameValidate;

impl cliclack::Validate<String> for JavaClassNameValidate {
type Err = crate::result::Error;

fn validate(&self, input: &String) -> Result<()> {
crate::class_names::validate_name(input)
}
}

#[derive(Clone, Copy, PartialEq, Eq)]
enum Subproject {
Fabric,
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

pub mod app;
pub mod class_names;
#[cfg(not(target_family = "wasm"))]
pub mod cli;
pub mod filer;
Expand Down
4 changes: 2 additions & 2 deletions src/templates/fabric/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ super::file_data!(BUILD_GRADLE build_gradle, "fabric", true, "build.gradle");

// Code
super::file_data!(FABRIC_MOD_JSON fabric_mod_json, "fabric", true, "src/main/resources/fabric.mod.json");
super::file_data!(MOD_CLASS mod_class, "fabric", true, "src/main/java/PACKAGE_DIR/fabric/ExampleModFabric.java");
super::file_data!(CLIENT_MOD_CLASS client_mod_class, "fabric", true, "src/main/java/PACKAGE_DIR/fabric/client/ExampleModFabricClient.java");
super::file_data_with_target!(MOD_CLASS mod_class, "fabric", true, "src/main/java/PACKAGE_DIR/fabric/ExampleModFabric.java", "src/main/java/PACKAGE_DIR/fabric/MAIN_CLASS_NAMEFabric.java");
super::file_data_with_target!(CLIENT_MOD_CLASS client_mod_class, "fabric", true, "src/main/java/PACKAGE_DIR/fabric/client/ExampleModFabricClient.java", "src/main/java/PACKAGE_DIR/fabric/client/MAIN_CLASS_NAMEFabricClient.java");

super::file_list!(pub all_files,
build_gradle
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
import net.fabricmc.api.ModInitializer;

//% if fabric_like
import %PACKAGE_NAME%.fabriclike.ExampleModFabricLike;
import %PACKAGE_NAME%.fabriclike.%MAIN_CLASS_NAME%FabricLike;
//% else
import %PACKAGE_NAME%.ExampleMod;
import %PACKAGE_NAME%.%MAIN_CLASS_NAME%;
//% end

public final class ExampleModFabric implements ModInitializer {
public final class %MAIN_CLASS_NAME%Fabric implements ModInitializer {
@Override
public void onInitialize() {
// This code runs as soon as Minecraft is in a mod-load-ready state.
Expand All @@ -17,10 +17,10 @@ public void onInitialize() {

//% if fabric_like
// Run the Fabric-like setup.
ExampleModFabricLike.init();
%MAIN_CLASS_NAME%FabricLike.init();
//% else
// Run our common setup.
ExampleMod.init();
%MAIN_CLASS_NAME%.init();
//% end
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import net.fabricmc.api.ClientModInitializer;

public final class ExampleModFabricClient implements ClientModInitializer {
public final class %MAIN_CLASS_NAME%FabricClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
// This entrypoint is suitable for setting up client-specific logic, such as rendering.
Expand Down
4 changes: 2 additions & 2 deletions src/templates/fabric/src/main/resources/fabric.mod.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
"environment": "*",
"entrypoints": {
"main": [
"%PACKAGE_NAME%.fabric.ExampleModFabric"
"%PACKAGE_NAME%.fabric.%MAIN_CLASS_NAME%Fabric"
],
"client": [
"%PACKAGE_NAME%.fabric.client.ExampleModFabricClient"
"%PACKAGE_NAME%.fabric.client.%MAIN_CLASS_NAME%FabricClient"
]
},
"mixins": [
Expand Down
2 changes: 1 addition & 1 deletion src/templates/fabric_like/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
super::file_data!(BUILD_GRADLE build_gradle, "fabric-like", true, "build.gradle");

// Code
super::file_data!(MOD_CLASS mod_class, "fabric-like", true, "src/main/java/PACKAGE_DIR/fabriclike/ExampleModFabricLike.java");
super::file_data_with_target!(MOD_CLASS mod_class, "fabric-like", true, "src/main/java/PACKAGE_DIR/fabriclike/ExampleModFabricLike.java", "src/main/java/PACKAGE_DIR/fabriclike/MAIN_CLASS_NAMEFabricLike.java");

super::file_list!(pub all_files,
build_gradle
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package %PACKAGE_NAME%.fabriclike;

import %PACKAGE_NAME%.ExampleMod;
import %PACKAGE_NAME%.%MAIN_CLASS_NAME%;

public final class ExampleModFabricLike {
public final class %MAIN_CLASS_NAME%FabricLike {
public static void init() {
// Run our common setup.
ExampleMod.init();
%MAIN_CLASS_NAME%.init();
}
}
2 changes: 1 addition & 1 deletion src/templates/forge/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ super::file_data!(GRADLE_PROPERTIES gradle_properties, "forge", true, "gradle.pr
// Code
super::file_data!(PACK_MCMETA pack_mcmeta, "forge", true, "src/main/resources/pack.mcmeta");
super::file_data!(MODS_TOML mods_toml, "forge", true, "src/main/resources/META-INF/mods.toml");
super::file_data!(MOD_CLASS mod_class, "forge", true, "src/main/java/PACKAGE_DIR/forge/ExampleModForge.java");
super::file_data_with_target!(MOD_CLASS mod_class, "forge", true, "src/main/java/PACKAGE_DIR/forge/ExampleModForge.java", "src/main/java/PACKAGE_DIR/forge/MAIN_CLASS_NAMEForge.java");

super::file_list!(pub all_files,
build_gradle
Expand Down
Loading