Skip to content

Commit

Permalink
Merge pull request #28 from andreluisos/27-create-project-runner
Browse files Browse the repository at this point in the history
Create BuildAndRunProject command
  • Loading branch information
andreluisos authored Nov 13, 2024
2 parents 12cd3ef + 7424c6b commit 053b0d3
Show file tree
Hide file tree
Showing 9 changed files with 366 additions and 5 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Among with bug fixing, I also plan to:
- [ ] Implement UI for JPA repository creation
- [x] Create Java files (enum, interface, annotation, class, record)
- [ ] Implement Spring Initializr and project loading
- [ ] Runner for Java applications
- [x] Runner for Java applications
- [ ] Figure out a way to create tests
- [ ] Implement Entity attribute/relationship editing
- [ ] ![Port to Java?](https://github.com/andreluisos/nvim-jpagenie/issues/9)
Expand Down
6 changes: 6 additions & 0 deletions lua/nvim_javagenie/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ end)

-- Keymaps
vim.api.nvim_set_keymap("n", "<leader>cj", "", { noremap = true, silent = true, desc = "JPA" })
vim.api.nvim_set_keymap(
"n",
"<leader>cjb",
":BuildAndRunProject<CR>",
{ noremap = true, silent = true, desc = "Build and run project" }
)
vim.api.nvim_set_keymap(
"n",
"<leader>cjn",
Expand Down
9 changes: 9 additions & 0 deletions rplugin/python3/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from pynvim.api.nvim import Nvim

from constants.java_basic_types import JAVA_BASIC_TYPES
from utils.build_helper import BuildHelper
from utils.java_file_utils import JavaFileLib
from utils.common_utils import CommonUtils
from utils.entity_creation_utils import EntityCreationUtils
Expand Down Expand Up @@ -72,3 +73,11 @@ def __init__(self, nvim: Nvim) -> None:
path_utils=self.path_utils,
common_utils=self.common_utils,
)
self.build_helper = BuildHelper(
nvim=self.nvim,
cwd=self.cwd,
path_utils=self.path_utils,
treesitter_utils=self.treesitter_utils,
common_utils=self.common_utils,
logging=self.logging,
)
22 changes: 22 additions & 0 deletions rplugin/python3/custom_types/project_properties.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from dataclasses import dataclass, field, InitVar
from pathlib import Path


@dataclass
class ProjectProperties:
project_name: str
project_group: str
project_version: str
project_build_dir: InitVar[str]
project_dir: InitVar[str]
project_root_dir: InitVar[str]
project_build_path: Path = field(init=False)
project_path: Path = field(init=False)
project_root_path: Path = field(init=False)

def __post_init__(
self, project_build_dir: str, project_dir: str, project_root_dir: str
):
self.project_build_path = Path(project_build_dir)
self.project_path = Path(project_dir)
self.project_root_path = Path(project_root_dir)
23 changes: 23 additions & 0 deletions rplugin/python3/project_runner_commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from pynvim.api import Nvim
from pynvim import List, command, plugin

from base import Base
from custom_types.log_level import LogLevel


@plugin
class JpaRepoCommands(Base):
def __init__(self, nvim: Nvim) -> None:
super().__init__(nvim)
self.debug: bool = False

@command("BuildAndRunProject", nargs="*")
def test(self, args: List[str]) -> None:
self.logging.reset_log_file()
self.logging.log(args, LogLevel.DEBUG)
if len(args) > 1:
error_msg = "Only one argument allowed"
self.logging.log(error_msg, LogLevel.ERROR)
raise ValueError(error_msg)
self.debug = True if "debug" in args else False
self.build_helper.run(self.debug)
264 changes: 264 additions & 0 deletions rplugin/python3/utils/build_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
from typing import Literal
from pynvim import Optional
from pynvim.api import Nvim
from custom_types.log_level import LogLevel
from custom_types.project_properties import ProjectProperties
from utils.common_utils import CommonUtils
from utils.treesitter_utils import TreesitterUtils
from utils.path_utils import PathUtils
from pathlib import Path
from platform import system
import subprocess
from utils.logging import Logging


class BuildHelper:
def __init__(
self,
nvim: Nvim,
cwd: Path,
path_utils: PathUtils,
treesitter_utils: TreesitterUtils,
common_utils: CommonUtils,
logging: Logging,
) -> None:
self.nvim = nvim
self.cwd = cwd
self.logging = logging
self.treesitter_utils = treesitter_utils
self.path_utils = path_utils
self.common_utils = common_utils
self.build_tool_type: Optional[Literal["maven", "gradle"]] = None
self.build_tool_path: Optional[Path] = self.get_build_tool_file_path()

def get_build_tool_file_path(self) -> Path:
os_identifier = system()
project_root_path = self.path_utils.get_project_root_path()
gradlew_file: Optional[Path] = None
maven_file: Optional[Path] = None
gradlew_file = next(
project_root_path.glob(
"**/gradlew.bat" if os_identifier == "Windows" else "**/gradlew"
),
None,
)
if gradlew_file:
self.build_tool_type = "gradle"
return gradlew_file
maven_file = next(
project_root_path.glob(
"**/mvnw.bat" if os_identifier == "Windows" else "**/mvnw"
),
None,
)
if maven_file:
self.build_tool_type = "maven"
return maven_file
error_msg = "Unable to get build tool"
self.logging.log(error_msg, LogLevel.ERROR)
raise FileNotFoundError(error_msg)

def get_maven_project_properties(
self, debug: bool = False
) -> Optional[ProjectProperties]:
project_properties: Optional[ProjectProperties] = None
if self.build_tool_type == "maven":
result = self.common_utils.run_subprocess(
[
f"{str(self.build_tool_path)}",
"help:evaluate",
"-Dexpression=project.name",
"-q",
"-DforceStdout",
],
debug,
)
project_name = result.stdout.strip()

result = self.common_utils.run_subprocess(
[
f"{str(self.build_tool_path)}",
"help:evaluate",
"-Dexpression=project.version",
"-q",
"-DforceStdout",
],
debug,
)
project_version = result.stdout.strip()

result = self.common_utils.run_subprocess(
[
f"{str(self.build_tool_path)}",
"help:evaluate",
"-Dexpression=project.groupId",
"-q",
"-DforceStdout",
],
debug,
)
project_group = result.stdout.strip()

project_build_dir = str(self.cwd / "target")
project_root_dir = str(self.cwd)
project_dir = str(self.cwd)

if project_name and project_group and project_version:
project_properties = ProjectProperties(
project_name=project_name,
project_group=project_group,
project_version=project_version,
project_build_dir=project_build_dir,
project_root_dir=project_root_dir,
project_dir=project_dir,
)
if debug:
self.logging.log(f"{project_properties}", LogLevel.DEBUG)
return project_properties

def get_gradle_project_properties(
self, debug: bool = False
) -> Optional[ProjectProperties]:
project_properties: Optional[ProjectProperties] = None
if self.build_tool_path and self.build_tool_type == "gradle":
result = self.common_utils.run_subprocess(
[f"{str(self.build_tool_path)}", "properties"], debug
)
project_name: Optional[str] = None
project_version: Optional[str] = None
project_group: Optional[str] = None
project_build_dir: Optional[str] = None
project_root_dir: Optional[str] = None
project_dir: Optional[str] = None
for line in result.stdout.splitlines():
if line.startswith("name:"):
project_name = line.split(":")[1].strip()
elif line.startswith("version:"):
project_version = line.split(":")[1].strip()
elif line.startswith("group:"):
project_group = line.split(":")[1].strip()
elif line.startswith("projectDir:"):
project_dir = line.split(":")[1].strip()
elif line.startswith("rootDir:"):
project_root_dir = line.split(":")[1].strip()
elif line.startswith("buildDir:"):
project_build_dir = line.split(":")[1].strip()

if (
project_name
and project_group
and project_version
and project_build_dir
and project_root_dir
and project_dir
):
project_properties = ProjectProperties(
project_name=project_name,
project_group=project_group,
project_version=project_version,
project_build_dir=project_build_dir,
project_root_dir=project_root_dir,
project_dir=project_dir,
)
if debug:
self.logging.log(f"{project_properties}", LogLevel.DEBUG)
return project_properties

def get_project_executable(
self, project_properties: ProjectProperties, debug: bool = False
) -> Optional[Path]:
executable_path = next(
project_properties.project_build_path.glob(
f"**/{project_properties.project_name}-{project_properties.project_version}.jar"
),
None,
)
if debug:
self.logging.log(f"Executable path: {executable_path}", LogLevel.DEBUG)
return executable_path

def maven_build(self) -> None:
self.logging.echomsg("Building")
output: Optional[str] = None
error: Optional[str] = None
try:
result = self.common_utils.run_subprocess(
[f"{str(self.build_tool_path)}", "package"]
)
output = " ".join(result.stdout.splitlines())
error = " ".join(result.stderr.splitlines())
if output and "BUILD SUCCESS" in output:
self.logging.echomsg("Build successful")
if "BUILD SUCCESS" not in output:
error_msg = "Unable to build"
self.logging.log(error_msg, LogLevel.ERROR)
raise ValueError(error_msg)
except subprocess.CalledProcessError as e:
output = " ".join(e.stdout.splitlines()) if e.stdout else None
error = " ".join(e.stderr.splitlines()) if e.stderr else None
except Exception as e:
error = str(e)
error_msg = f"Unexpected error: {error}"
self.logging.echomsg(error_msg)
self.logging.log(error_msg, LogLevel.ERROR)
raise ValueError(error_msg)

def gradle_build(self) -> None:
self.logging.echomsg("Building")
output: Optional[str] = None
error: Optional[str] = None
try:
result = self.common_utils.run_subprocess(
[f"{str(self.build_tool_path)}", "build"]
)
output = " ".join(result.stdout.splitlines())
error = " ".join(result.stderr.splitlines())
if output and "BUILD SUCCESSFUL" in output:
self.logging.echomsg("Build successful")
if "BUILD SUCCESSFUL" not in output:
error_msg = "Unable to build"
self.logging.log(error_msg, LogLevel.ERROR)
raise ValueError(error_msg)
except subprocess.CalledProcessError as e:
output = " ".join(e.stdout.splitlines()) if e.stdout else None
error = " ".join(e.stderr.splitlines()) if e.stderr else None
except Exception as e:
error = str(e)
error_msg = f"Unexpected error: {error}"
self.logging.echomsg(error_msg)
self.logging.log(error_msg, LogLevel.ERROR)
raise ValueError(error_msg)

def run(self, debug: bool = False) -> None:
if self.build_tool_type == "gradle":
self.gradle_build()
project_properties = self.get_gradle_project_properties(debug)
if not project_properties:
error_msg = "Unable to get project's properties"
self.logging.echomsg(error_msg)
self.logging.log(error_msg, LogLevel.ERROR)
raise ValueError(error_msg)
project_executable = self.get_project_executable(project_properties)
if not project_executable:
error_msg = "Unable to find built executable"
self.logging.echomsg(error_msg)
self.logging.log(error_msg, LogLevel.ERROR)
raise ValueError(error_msg)
else:
self.maven_build()
project_properties = self.get_maven_project_properties(debug)
if not project_properties:
error_msg = "Unable to get project's properties"
self.logging.echomsg(error_msg)
self.logging.log(error_msg, LogLevel.ERROR)
raise ValueError(error_msg)
project_executable = self.get_project_executable(project_properties)
if not project_executable:
error_msg = "Unable to find built executable"
self.logging.echomsg(error_msg)
self.logging.log(error_msg, LogLevel.ERROR)
raise ValueError(error_msg)
java_executable = self.path_utils.get_java_executable_path()
self.nvim.command(
f"split | terminal {str(java_executable)} -jar {str(project_executable)}"
)
30 changes: 29 additions & 1 deletion rplugin/python3/utils/common_utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from re import sub
from subprocess import run, CompletedProcess, CalledProcessError
from typing import List, Optional


Expand Down Expand Up @@ -139,7 +140,7 @@ def get_java_file_data(
)

def get_all_java_files_data(self, debug: bool = False) -> List[JavaFileData]:
root_path = self.path_utils.get_spring_project_root_path()
root_path = self.path_utils.get_project_root_path()
files_found: List[JavaFileData] = []
for p in root_path.rglob("*.java"):
if "main" not in p.parts:
Expand Down Expand Up @@ -213,3 +214,30 @@ def construct_file_path(
if debug:
self.logging.log(f"File path: {str(file_path)}", LogLevel.DEBUG)
return file_path

def run_subprocess(
self, command: list[str], debug: bool = False
) -> CompletedProcess:
try:
result = run(
command,
capture_output=True,
text=True,
check=True,
)
if debug:
self.logging.log(f"Command: {' '.join(command)}", LogLevel.DEBUG)
self.logging.log(f"Output: {result.stdout}", LogLevel.DEBUG)
self.logging.log(f"Error: {result.stderr}", LogLevel.DEBUG)
return result
except CalledProcessError as e:
if debug:
self.logging.log(f"Command failed: {' '.join(command)}", LogLevel.DEBUG)
self.logging.log(f"Output: {e.stdout}", LogLevel.DEBUG)
self.logging.log(f"Error: {e.stderr}", LogLevel.DEBUG)
raise
except Exception as e:
error_msg = f"Unexpected error: {str(e)}"
self.logging.echomsg(error_msg)
self.logging.log(error_msg, LogLevel.ERROR)
raise ValueError(error_msg)
Loading

0 comments on commit 053b0d3

Please sign in to comment.