import re from pathlib import Path import sys class Colors: HEADER = '\033[95m' OK_BLUE = '\033[94m' OK_GREEN = '\033[92m' WARNING = '\033[93m' FAIL = '\033[91m' BOLD = '\033[1m' GRAY = '\033[38;5;8m' RESET = '\033[0m' class I: """Interaction-related methods""" @staticmethod def title(title_text: str): print(Colors.BOLD + '\n' + title_text + '\n' + Colors.RESET) @staticmethod def ask(question: str, default: str = None, show_default: bool = True): while True: answer = input(question + (' [' + default + ']' if default and show_default else '') + ' ') if answer: return answer elif default is not None: return default @classmethod def ask_bool(cls, question: str, default: bool): options_invite, default_str = ('[Yn]', 'y') if default else ('[yN]', 'n') while True: answer = cls.ask(question + ' ' + options_invite, default=default_str, show_default=False).lower() if answer in ['y', 'n']: return answer == 'y' class StringUtils: _first_cap_re = re.compile('(.)([A-Z][a-z]+)') _all_cap_re = re.compile('([a-z0-9])([A-Z])') @classmethod def camel_case_to_snake_case(cls, camel_name): s1 = StringUtils._first_cap_re.sub(r'\1_\2', camel_name) return StringUtils._all_cap_re.sub(r'\1_\2', s1).lower() @staticmethod def create_java_class_name(raw_name): first, *rest = raw_name.replace('-', '_').replace(' ', '_').split('_') return first[0].upper() + first[1:] + ''.join(word[0].upper() + word[1:] for word in rest) @staticmethod def indent(text: str, level): indented = '' for line in text.strip('\n').split('\n'): indented += (' ' * 4 * level) + line + '\n' return indented class BukkitPluginGenerator: GITIGNORE_TEMPLATE = '''# Created by the zLib plugin bootstrap generator # Inspired by https://www.gitignore.io/api/java,maven,intellij,eclipse,netbeans ### Maven ### target/ pom.xml.tag pom.xml.releaseBackup pom.xml.versionsBackup pom.xml.next release.properties dependency-reduced-pom.xml buildNumber.properties .mvn/timing.properties ### Intellij ### # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 ## Folder-based project format .idea/ ## File-based project format *.iws *.iml ## Plugin-specific files # IntelliJ /out/ # mpeltonen/sbt-idea plugin .idea_modules/ # JIRA plugin atlassian-ide-plugin.xml # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties ### Intellij Patch ### # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 # *.iml # modules.xml ### Eclipse ### .metadata bin/ tmp/ *.tmp *.bak *.swp *~.nib local.properties .settings/ .loadpath .recommenders # Eclipse Core .project # External tool builders .externalToolBuilders/ # Locally stored "Eclipse launch configurations" *.launch # PyDev specific (Python IDE for Eclipse) *.pydevproject # CDT-specific (C/C++ Development Tooling) .cproject # JDT-specific (Eclipse Java Development Tools) .classpath # Java annotation processor (APT) .factorypath # PDT-specific (PHP Development Tools) .buildpath # sbteclipse plugin .target # Tern plugin .tern-project # TeXlipse plugin .texlipse # STS (Spring Tool Suite) .springBeans # Code Recommenders .recommenders/ ### NetBeans ### nbproject/private/ build/ nbbuild/ dist/ nbdist/ nbactions.xml .nb-gradle/ ### Java ### *.class # Mobile Tools for Java (J2ME) .mtj.tmp/ # Package Files # *.jar *.war *.ear # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* ''' MAVEN_TEMPLATE = ''' 4.0.0 {groupId} {artifactId} {version} jar UTF-8 {java_version} {java_version} {build} spigot-repo https://hub.spigotmc.org/nexus/content/groups/public/ {zlib_repo} org.bukkit bukkit 1.9-R0.1-SNAPSHOT {zlib_dependency} ''' MAVEN_ZLIB_SHADING_TEMPLATE = ''' org.apache.maven.plugins maven-shade-plugin 2.3 true fr.zcraft:zlib fr.zcraft.zlib {pckg}.zlib package shade ''' MAVEN_ZLIB_REPO_TEMPLATE = ''' zDevelopers http://maven.carrade.eu/artifactory/snapshots ''' MAVEN_ZLIB_DEPENDENCY_TEMPLATE = ''' fr.zcraft zlib 0.99-SNAPSHOT ''' MAIN_CLASS_TEMPLATE = '''package {package}; {imports} public final class {class_name} extends {base_class} {{ private static {class_name} instance; @Override public void onEnable() {{ instance = this;{on_enable} }} public static {class_name} get() {{ return instance; }} }} ''' LISTENER_TEMPLATE = '''package {package}; import org.bukkit.event.Listener; {imports} public final class {class_name} {extends}implements Listener {{ // TODO implement events listeners }} ''' COMMAND_ZLIB_TEMPLATE = '''package {package}; import fr.zcraft.zlib.components.commands.Command; import fr.zcraft.zlib.components.commands.CommandException; import fr.zcraft.zlib.components.commands.CommandInfo; import java.util.List; @CommandInfo (name = "{sub_command_name}", usageParameters = "") public final class {class_name} extends Command {{ @Override protected void run() throws CommandException {{ // TODO implement command /{command_name} {sub_command_name} }} @Override protected List complete() throws CommandException {{ // TODO implement auto-completion for /{command_name} {sub_command_name} return null; }} }} ''' COMMAND_BUKKIT_TEMPLATE = '''package {package}; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.command.TabCompleter; import java.util.List; public class {class_name} implements CommandExecutor, TabCompleter {{ @Override public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {{ // TODO implement command /{command_name} }} @Override public List onTabComplete(CommandSender sender, Command cmd, String label, String[] args) {{ // TODO implement auto-completion for /{command_name} return null; }} }} ''' def __init__(self, folder: Path, name: str, package: str, main_class: str, version: str, author: str = None, website: str = None, description: str = None, load_at_startup: bool = False, zlib: bool = True, java_version: str = '1.7', gitignore: bool = True, stdout=None, stderr=None): self.folder = folder self.name = name self.package = package self.main_class = main_class self.version = version self.author = author self.website = website self.description = description self.load_at_startup = load_at_startup self.zlib = zlib self.java_version = java_version self.gitignore = gitignore self.listeners = [] self.commands = [] self.stdout = stdout self.stderr = stderr self._folder_main = self.folder / 'src/main' self._folder_java = self._folder_main / 'java' self._folder_resources = self._folder_main / 'resources' self._folder_root_package = self._folder_java / (self.package.replace('.', '/')) self._folder_commands = self._folder_root_package / 'commands' self._folder_listeners = self._folder_root_package / 'listeners' def add_command(self, command): self.commands.append(command) def add_listener(self, listener: str): self.listeners.append(listener) def generate(self): if self.gitignore: self._save_file('.gitignore', self.GITIGNORE_TEMPLATE) self._save_file('pom.xml', self._generate_maven()) self._save_file('plugin.yml', self._generate_plugin_yml(), self._folder_resources) self._save_file(self.main_class + '.java', self._generate_main_class(), self._folder_root_package) self._generate_listeners() self._generate_commands() def _save_file(self, relative_name: str, content: str, root: Path = None): if root is None: root = self.folder file_path = root / relative_name parent = file_path.parent if not parent.exists(): file_path.parent.mkdir(parents=True) elif not parent.is_dir(): if self.stderr: self.stderr.write( Colors.FAIL + 'Cannot create folder {0}: a non-folder file already exists' + Colors.RESET) return file_path.touch(exist_ok=True) with file_path.open(mode='w') as f: f.write(content) if self.stdout: self.stdout.write('Wrote file {0}\n'.format(str(file_path))) def _generate_maven(self): artifact = StringUtils.create_java_class_name(self.name) group = self.package.replace('.' + artifact, '') return self.MAVEN_TEMPLATE.format( groupId=group, artifactId=artifact, version=self.version, java_version=self.java_version, build=self.MAVEN_ZLIB_SHADING_TEMPLATE.format(pckg=self.package) if self.zlib else '', zlib_repo=self.MAVEN_ZLIB_REPO_TEMPLATE if self.zlib else '', zlib_dependency=self.MAVEN_ZLIB_DEPENDENCY_TEMPLATE if self.zlib else '' ) def _generate_plugin_yml(self): plugin_yml = '''name: {0}\nversion: {1}\nmain: {2}.{3}\n''' \ .format(self.name, self.version, self.package, self.main_class) if self.author or self.website or self.description: plugin_yml += '\n' if self.description: plugin_yml += 'description: {0}\n'.format(self.description) if self.author: plugin_yml += 'author: {0}\n'.format(self.author) if self.website: plugin_yml += 'website: {0}\n'.format(self.website) if self.load_at_startup: plugin_yml += '\nload: STARTUP\n' if self.commands: plugin_yml += '\ncommands:\n' for command in self.commands: plugin_yml += ' {0}:\n description: {1}\n'.format(command['name'], command['description']) return plugin_yml def _generate_main_class(self): on_enable = '' imports = [] if self.zlib: components = [] base_class = 'ZPlugin' imports.append('fr.zcraft.zlib.core.ZPlugin') if self.commands: components.append('Commands.class') imports.append('fr.zcraft.zlib.components.commands.Commands') for listener in self.listeners: components.append(listener + '.class') imports.append(self.package + '.listeners.' + listener) on_enable = 'loadComponents(' + ', '.join(components) + ');\n' if self.commands: on_enable += '\n' for command in self.commands: sub_commands_classes = [] for sub_command_name in command['sub_commands']: class_name = self.__generate_zlib_command_class_name(command['name'], sub_command_name) sub_commands_classes.append(class_name + '.class') imports.append(self.package + '.commands.' + command['name'].lower() + '.' + class_name) on_enable += 'Commands.register("{name}"{sub_commands});\n'.format( name=command['name'], sub_commands=', ' + ', '.join(sub_commands_classes) if command['sub_commands'] else '' ) else: base_class = 'JavaPlugin' imports.append('org.bukkit.plugin.java.JavaPlugin') for listener in self.listeners: imports.append(self.package + '.listeners.' + listener) on_enable += 'getServer().getPluginManager().registerEvents(new {0}(), this);\n'.format(listener) if self.commands: on_enable += '\n' for command in self.commands: class_name = self.__generate_bukkit_command_class_name(command['name']) imports.append(self.package + '.commands.' + class_name) on_enable += 'getCommand("{0}").setExecutor(new {1}());\n'.format(command['name'], class_name) return self.MAIN_CLASS_TEMPLATE.format( package=self.package, class_name=self.main_class, base_class=base_class, imports='\n'.join('import {0};'.format(class_name) for class_name in imports), on_enable='\n\n' + StringUtils.indent(on_enable.strip(), 2) if on_enable else '' ) def _generate_listeners(self): for listener in self.listeners: self._save_file(listener + '.java', self._generate_listener(listener), self._folder_listeners) def _generate_listener(self, listener): return self.LISTENER_TEMPLATE.format( package=self.package + '.listeners', imports='import fr.zcraft.zlib.core.ZLibComponent;\n' if self.zlib else '', class_name=listener, extends='extends ZLibComponent ' if self.zlib else '' ) def _generate_commands(self): for command in self.commands: if self.zlib: package_folder = self._folder_commands / command['name'].lower() for sub_command in command['sub_commands']: self._save_file(self.__generate_zlib_command_class_name(command['name'], sub_command) + '.java', self._generate_command_zlib(command['name'], sub_command), package_folder) else: self._save_file(self.__generate_bukkit_command_class_name(command['name']) + '.java', self._generate_command_bukkit(command['name']), self._folder_commands) def _generate_command_zlib(self, command_name, sub_command_name): return self.COMMAND_ZLIB_TEMPLATE.format( package=self.package + '.commands.' + command_name.lower(), command_name=command_name, sub_command_name=sub_command_name, class_name=self.__generate_zlib_command_class_name(command_name, sub_command_name) ) def _generate_command_bukkit(self, command_name): return self.COMMAND_BUKKIT_TEMPLATE.format( package=self.package + '.commands', command_name=command_name, class_name=self.__generate_bukkit_command_class_name(command_name) ) @staticmethod def __generate_zlib_command_class_name(command_name: str, sub_command_name: str): return command_name.capitalize() + sub_command_name.capitalize() + 'Command' @staticmethod def __generate_bukkit_command_class_name(command_name: str): return command_name.capitalize() + 'Command' if __name__ == '__main__': print(Colors.HEADER + 'Bukkit plugin bootstrap generator' + Colors.RESET) I.title('The basics') name = I.ask('What\'s your plugin name?') version = I.ask('What\'s your plugin version?', '1.0') author = I.ask('What\'s your name?', '') website = I.ask('Enter the plugin\'s website, if it exists.', '') description = I.ask('Enter a short description if you want.', '') I.title('Technical basics') java_version = I.ask('Enter the Java version you want to use (1.7 or 1.8).', '1.7') package = I.ask('What package do you want to use?') main_class = I.ask('Enter the name of your main class.', StringUtils.create_java_class_name(name)) load_at_startup = I.ask_bool('Do you want your plugin to be loaded at startup? ' 'Required if you plan to alter the map generation.', False) print(Colors.GRAY) print('We, the authors of this bootstrap script, also wrote a pretty-complete development library called zLib\n' '(because we are the zDevelopers), with lots of features to make development easier, like descent commands\n' 'managements, great tools to create beautiful GUIs and scoreboards with no effort, internationalization\n' 'support, and many many other big and small tools.\n') print('Check it out at https://github.com/zDevelopers/zLib .') print(Colors.RESET) zlib = I.ask_bool('Do you want to use zLib? The generated code will use it instead of old Bukkit methods.', True) I.title('Git') gitignore = I.ask_bool('Do you want us to generate a .gitignore file?', True) I.title('Listeners generation') listeners = [] if I.ask_bool('Do you want us to add listeners for you?', True): print() while True: listener = I.ask('Enter a listener name. An empty name ends.', '') if not listener: break listeners.append(listener) I.title('Commands') commands = [] if I.ask_bool('Do you want us to add commands for you?', True): while True: command_name = I.ask('\nEnter the name of a command. An empty name ends.', '') if not command_name: break if command_name.startswith('/'): command_name = command_name[1:] command_description = I.ask('Enter a short description, if you want.', '') sub_commands = [] if zlib: sub_commands_raw = I.ask('Enter the name of the /{0} sub-commands, ' 'space-separated.'.format(command_name), '').split() for sub_command in sub_commands_raw: sub_commands.append(sub_command.strip()) commands.append({ 'name': command_name, 'description': command_description, 'sub_commands': sub_commands }) I.title('Files location') current_directory = Path('.') folder_full = None while True: folder = I.ask('Type the folder where the plugin will be generated.', name.lower().replace(' ', '_')) folder_full = current_directory / folder if folder_full.exists(): print(Colors.FAIL + 'Error: please select another directory as {0} already exists.'.format( str(folder_full)) + Colors.RESET) else: break I.title('Generating...') generator = BukkitPluginGenerator( folder=folder_full, name=name, package=package, main_class=main_class, version=version, author=author, website=website, description=description, load_at_startup=load_at_startup, zlib=zlib, java_version=java_version, stdout=sys.stdout, stderr=sys.stderr ) [generator.add_listener(listener) for listener in listeners] [generator.add_command(command) for command in commands] generator.generate() print(Colors.BOLD + '\nDone.' + Colors.RESET)