diff --git a/.github/workflows/on-pr-main.yml b/.github/workflows/on-pr-main.yml index 0d1029e..58ac411 100644 --- a/.github/workflows/on-pr-main.yml +++ b/.github/workflows/on-pr-main.yml @@ -4,6 +4,7 @@ on: pull_request: branches: - main + - dev permissions: pull-requests: write diff --git a/documentation/CONTRIBUTING.md b/documentation/CONTRIBUTING.md index d5bbf86..b368e6f 100644 --- a/documentation/CONTRIBUTING.md +++ b/documentation/CONTRIBUTING.md @@ -12,7 +12,7 @@ 풀 리퀘스트를 보내실 때, 아래의 사항들을 유의해주세요: -- `main` 브랜치를 기준으로 하여 풀 리퀘스트를 보내주세요. +- `dev` 브랜치를 기준으로 하여 풀 리퀘스트를 보내주세요. - 변경사항에 대한 명확하고 간결한 설명을 포함해주세요. - 변경된 코드에 대한 적절한 테스트가 수행되었는지 확인해주세요. diff --git a/documentation/DOCUMENTATION.md b/documentation/DOCUMENTATION.md index 43e2a6f..4e09840 100644 --- a/documentation/DOCUMENTATION.md +++ b/documentation/DOCUMENTATION.md @@ -9,7 +9,7 @@ ### 생성되는 소스코드 폴더명 변경하기 -config.json의 `srcDirPrefix.value`을 수정하여, 생성되는 소스코드 폴더명의 prefix를 변경할 수 있습니다. +`config.json`의 `srcDirPrefix.value`을 수정하여, 생성되는 소스코드 폴더명의 `prefix`를 변경할 수 있습니다. 예시로 1000번 문제에서 `srcDirPrefix.value`을 `BOJ_`로 설정하면, `BOJ_1000` 폴더가 생성됩니다. 기본 설정 값은 `p`입니다. ```json @@ -42,7 +42,7 @@ config.json의 `srcDirPrefix.value`을 수정하여, 생성되는 소스코드 import java.util.Scanner; /* - BAEKJOON {{number}} {{title}} + BAEKJOON {{number}}번 {{title}} https://www.acmicpc.net/problem/{{number}} */ @@ -94,3 +94,43 @@ public class Main { } } ``` + +### 생성되는 README.md 파일의 템플릿 변경하기 + +`config.json`의 `markdownTemplate.value`을 수정하여, 생성되는 `README.md`의 파일 내용을 변경할 수 있습니다. +기본 설정 값은 아래와 같습니다. + +```markdown +# {{title}} + +> 문제 번호 : {{number}}
+> 출처 : {{url}} + +## 문제 설명 + +{{description}} +``` + +#### 예악어 + +해당 기능에선 네가지 예약어를 지원합니다. + +- `{{title}}`: 문제 제목 +- `{{number}}`: 문제 번호 +- `{{url}}`: 문제 출처 URL +- `{{description}}`: 문제 설명 *(html 태그 포함)* + +#### 비활성화 방법 + +`config.json`의 `enableReadme.value`를 `"true"` 대신 `"false"`로 수정하면, README.md 파일이 생성되지 않습니다. boolean +형태가 아닌 `"`로 감싸진 문자열로 입력해야 합니다. + +```json +{ + ... + "enableReadme": { + "value": "false" + }, + ... +} +``` diff --git a/src/main/java/kr/huni/BojStarter.java b/src/main/java/kr/huni/BojStarter.java index f3ef617..dd1d3d6 100644 --- a/src/main/java/kr/huni/BojStarter.java +++ b/src/main/java/kr/huni/BojStarter.java @@ -1,13 +1,14 @@ package kr.huni; import java.io.IOException; -import kr.huni.code_generator.CodeGenerator; +import kr.huni.code_generator.FileContentGenerator; import kr.huni.code_generator.GeneratedCode; import kr.huni.code_runner.CodeOpenManager; import kr.huni.file_generator.JavaSourceCodeFile; import kr.huni.os.OperatingSystem; import kr.huni.problem_parser.BaekjoonProblemParser; import kr.huni.problem_parser.Problem; +import kr.huni.user_configuration.UserConfigurationLoader; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -17,12 +18,16 @@ public class BojStarter { private final CodeOpenManager codeOpenManager; private final JavaSourceCodeFile fileUtil; - private final CodeGenerator codeGenerator; + private final FileContentGenerator codeGenerator; private final BaekjoonProblemParser problemParser; public void run(final int problemNumber) { Problem problem = problemParser.parse(problemNumber); - GeneratedCode generatedCode = codeGenerator.generate(problem); + if (!problem.isExist()) { + log.error("문제를 찾을 수 없습니다."); + return; + } + GeneratedCode generatedCode = codeGenerator.generateCode(problem); createSrcFile(problem, generatedCode); openSourceCodeWithIde(problem); @@ -37,7 +42,7 @@ private void openSourceCodeWithIde(Problem problem) { IntelliJ IDEA의 idea 명령어가 설치되어 있지 않습니다. 직접 IntelliJ IDEA를 실행해서 프로젝트를 열어주세요. 생성된 프로젝트 경로 : {} - %n + 상세 오류 : """, problem.getSourceRootDirectory(), e); } } @@ -45,7 +50,8 @@ private void openSourceCodeWithIde(Problem problem) { private void createSrcFile(Problem problem, GeneratedCode generatedCode) { try { fileUtil.write(problem.getSourceRootDirectory(), generatedCode.mainCode(), - generatedCode.testCode()); + generatedCode.testCode(), codeGenerator.generateMarkdown(problem), + UserConfigurationLoader.getInstance().enableReadme.getValue().equals("true")); } catch (IOException e) { log.error("소스코드 파일 또는 디렉토리 생성에 실패했습니다.", e); } diff --git a/src/main/java/kr/huni/code_generator/CodeGenerator.java b/src/main/java/kr/huni/code_generator/CodeGenerator.java deleted file mode 100644 index c7bec65..0000000 --- a/src/main/java/kr/huni/code_generator/CodeGenerator.java +++ /dev/null @@ -1,8 +0,0 @@ -package kr.huni.code_generator; - -import kr.huni.problem_parser.Problem; - -public interface CodeGenerator { - - GeneratedCode generate(Problem problem); -} diff --git a/src/main/java/kr/huni/code_generator/FileContentGenerator.java b/src/main/java/kr/huni/code_generator/FileContentGenerator.java new file mode 100644 index 0000000..e7c4a93 --- /dev/null +++ b/src/main/java/kr/huni/code_generator/FileContentGenerator.java @@ -0,0 +1,10 @@ +package kr.huni.code_generator; + +import kr.huni.problem_parser.Problem; + +public interface FileContentGenerator { + + GeneratedCode generateCode(Problem problem); + + String generateMarkdown(Problem problem); +} diff --git a/src/main/java/kr/huni/code_generator/SourceCodeTemplate.java b/src/main/java/kr/huni/code_generator/FileContentTemplate.java similarity index 72% rename from src/main/java/kr/huni/code_generator/SourceCodeTemplate.java rename to src/main/java/kr/huni/code_generator/FileContentTemplate.java index 569c4cb..6a2f0b5 100644 --- a/src/main/java/kr/huni/code_generator/SourceCodeTemplate.java +++ b/src/main/java/kr/huni/code_generator/FileContentTemplate.java @@ -4,18 +4,20 @@ import java.util.List; import kr.huni.problem_parser.TestCase; -public interface SourceCodeTemplate { +public interface FileContentTemplate { String TEST_JAVA_FILE = "code_sample/TestHelper.java"; String NO_TEST_JAVA_FILE = "code_sample/NoTestHelper.java"; String REPLACED_NUMBER = "{{number}}"; String REPLACED_TITLE = "{{title}}"; + String REPLACED_DESCRIPTION = "{{description}}"; + String REPLACED_URL = "{{url}}"; String REPLACED_TEST_CASES = "// {{test_case}}"; String DEFAULT_MAIN_CODE_TEMPLATE = """ import java.util.Scanner; /* - BAEKJOON {{number}} {{title}} + BAEKJOON {{number}}번 {{title}} https://www.acmicpc.net/problem/{{number}} */ @@ -27,6 +29,17 @@ public static void main(String[] args) { } } """; + String DEFAULT_MARKDOWN_TEMPLATE = """ + # {{title}} + + > 문제 번호 : {{number}} \s + > 출처 : {{url}} + + ## 문제 설명 + + {{description}} + + """; /** * Main.java 소스코드 문자를 생성하고 반환합니다. @@ -45,4 +58,6 @@ public static void main(String[] args) { */ String getTestCode(List testCases) throws IOException; + String getMarkdownContent(int number, String title, String description); + } diff --git a/src/main/java/kr/huni/code_generator/JavaCodeGenerator.java b/src/main/java/kr/huni/code_generator/JavaCodeGenerator.java index 5075c85..a30e5b2 100644 --- a/src/main/java/kr/huni/code_generator/JavaCodeGenerator.java +++ b/src/main/java/kr/huni/code_generator/JavaCodeGenerator.java @@ -5,16 +5,16 @@ import lombok.extern.slf4j.Slf4j; @Slf4j -public class JavaCodeGenerator implements CodeGenerator { +public class JavaCodeGenerator implements FileContentGenerator { @Override - public GeneratedCode generate(Problem problem) { + public GeneratedCode generateCode(Problem problem) { if (problem.getTestCases() == null) { throw new IllegalArgumentException("테스트는 null이 될 수 없습니다."); } try { - SourceCodeTemplateImpl sourceCodeTemplate = new SourceCodeTemplateImpl(); + JavaTemplate sourceCodeTemplate = new JavaTemplate(); String mainCode = sourceCodeTemplate.getMainCode(problem.getNumber(), problem.getTitle()); String testCode = sourceCodeTemplate.getTestCode(problem.getTestCases()); @@ -25,4 +25,11 @@ public GeneratedCode generate(Problem problem) { } } + @Override + public String generateMarkdown(Problem problem) { + JavaTemplate markdownTemplate = new JavaTemplate(); + return markdownTemplate.getMarkdownContent(problem.getNumber(), problem.getTitle(), + problem.getDescription()); + } + } diff --git a/src/main/java/kr/huni/code_generator/SourceCodeTemplateImpl.java b/src/main/java/kr/huni/code_generator/JavaTemplate.java similarity index 65% rename from src/main/java/kr/huni/code_generator/SourceCodeTemplateImpl.java rename to src/main/java/kr/huni/code_generator/JavaTemplate.java index be10ae2..5d658fb 100644 --- a/src/main/java/kr/huni/code_generator/SourceCodeTemplateImpl.java +++ b/src/main/java/kr/huni/code_generator/JavaTemplate.java @@ -1,5 +1,7 @@ package kr.huni.code_generator; +import static kr.huni.problem_parser.BaekjoonProblemParser.PROBLEM_URL; + import java.io.IOException; import java.util.List; import kr.huni.file_generator.SourceCodeFile; @@ -7,7 +9,7 @@ import kr.huni.user_configuration.UserConfigurationField; import kr.huni.user_configuration.UserConfigurationLoader; -public class SourceCodeTemplateImpl implements SourceCodeTemplate { +public class JavaTemplate implements FileContentTemplate { public String getMainCode(int number, String title) { String template = DEFAULT_MAIN_CODE_TEMPLATE; @@ -47,4 +49,22 @@ public String getTestCode(List testCases) throws IOException { String template = SourceCodeFile.readFileFromResource(TEST_JAVA_FILE); return template.replace(REPLACED_TEST_CASES, testCaseCode.toString()); } + + @Override + public String getMarkdownContent(int number, String title, String description) { + String template = DEFAULT_MARKDOWN_TEMPLATE; + UserConfigurationField markdownTemplate = + UserConfigurationLoader.getInstance().markdownTemplate; + boolean useCustomTemplate = markdownTemplate.getValue() != null; + + if (useCustomTemplate) { + template = markdownTemplate.getValue(); + } + + return template + .replace(REPLACED_NUMBER, String.valueOf(number)) + .replace(REPLACED_TITLE, title) + .replace(REPLACED_DESCRIPTION, description) + .replace(REPLACED_URL, PROBLEM_URL + number); + } } diff --git a/src/main/java/kr/huni/code_runner/CodeOpenCommand.java b/src/main/java/kr/huni/code_runner/CodeOpenCommand.java deleted file mode 100644 index 722f436..0000000 --- a/src/main/java/kr/huni/code_runner/CodeOpenCommand.java +++ /dev/null @@ -1,8 +0,0 @@ -package kr.huni.code_runner; - -public record CodeOpenCommand( - String ideExistedCommand, - String executeCommand -) { - -} diff --git a/src/main/java/kr/huni/code_runner/CodeOpenManager.java b/src/main/java/kr/huni/code_runner/CodeOpenManager.java index e74aa9f..c9b97ff 100644 --- a/src/main/java/kr/huni/code_runner/CodeOpenManager.java +++ b/src/main/java/kr/huni/code_runner/CodeOpenManager.java @@ -10,6 +10,7 @@ public interface CodeOpenManager { * @throws IOException 실행 실패 * @implSpec 주어진 codePath를 IDE로 열어준다 */ - void run(String codePath, OperatingSystem operatingSystem) throws IOException; + void run(String codePath, OperatingSystem operatingSystem) + throws IOException; } diff --git a/src/main/java/kr/huni/code_runner/IdeaCodeOpenManager.java b/src/main/java/kr/huni/code_runner/IdeaCodeOpenManager.java index 91a7751..ea461b0 100644 --- a/src/main/java/kr/huni/code_runner/IdeaCodeOpenManager.java +++ b/src/main/java/kr/huni/code_runner/IdeaCodeOpenManager.java @@ -6,14 +6,11 @@ public class IdeaCodeOpenManager implements CodeOpenManager { public void run(String codePath, OperatingSystem os) throws IOException { - CodeOpenCommand command = switch (os) { - case WINDOWS -> new CodeOpenCommand("where idea", "idea"); - case LINUX, MAC -> new CodeOpenCommand("which idea", "idea"); + String command = switch (os) { + case WINDOWS -> "idea.bat"; + case LINUX, MAC -> "idea"; }; - boolean ideaExist = Runtime.getRuntime().exec(command.ideExistedCommand()).exitValue() == 0; - if (ideaExist) { - Runtime.getRuntime().exec(command.executeCommand() + " " + codePath); - } + Runtime.getRuntime().exec(command + " " + codePath); } } diff --git a/src/main/java/kr/huni/file_generator/JavaSourceCodeFile.java b/src/main/java/kr/huni/file_generator/JavaSourceCodeFile.java index 3e92209..a270b09 100644 --- a/src/main/java/kr/huni/file_generator/JavaSourceCodeFile.java +++ b/src/main/java/kr/huni/file_generator/JavaSourceCodeFile.java @@ -8,7 +8,8 @@ public class JavaSourceCodeFile implements SourceCodeFile { @Override - public void write(String sourceRootDirectory, String sourceCode, String testCode) + public void write(String sourceRootDirectory, String sourceCode, String testCode, String readme, + boolean enableReadme) throws IOException { File srcDir = new File(sourceRootDirectory, "src"); @@ -18,6 +19,13 @@ public void write(String sourceRootDirectory, String sourceCode, String testCode log.info("소스코드 디렉토리 생성 완료"); writeToFile(srcDir, "Main.java", sourceCode); writeToFile(srcDir, "TestHelper.java", testCode); + log.info("소스코드 파일 생성 완료"); + + if (enableReadme) { + writeToFile(srcDir, "README.md", readme); + log.info("README.md 파일 생성 완료"); + } } + } diff --git a/src/main/java/kr/huni/file_generator/SourceCodeFile.java b/src/main/java/kr/huni/file_generator/SourceCodeFile.java index 115ac55..703c9e6 100644 --- a/src/main/java/kr/huni/file_generator/SourceCodeFile.java +++ b/src/main/java/kr/huni/file_generator/SourceCodeFile.java @@ -8,7 +8,7 @@ import java.io.InputStreamReader; import java.util.Objects; import java.util.Scanner; -import kr.huni.code_generator.SourceCodeTemplateImpl; +import kr.huni.code_generator.JavaTemplate; /** * 소스코드 파일을 생성하고, 내용을 채워주기 위한 인터페이스 @@ -25,16 +25,18 @@ public interface SourceCodeFile { * @implSpec 해당 메서드안에서 필요한 하위 폴더를 생성하고, {@link #writeToFile(File, String, String)}를 통해 알고리즘을 구현할 * 소스코드 파일과 테스트 코드 파일을 생성해야합니다. */ - void write(String directory, String sourceCode, String testCode) throws IOException; + void write(String directory, String sourceCode, String testCode, String readme, + boolean enableReadme) + throws IOException; /** * 파일을 생성하고, 내용을 채웁니다. * - * @param srcDir 소스코드를 저장할 위치가 담긴 객체 - * @param fileName 파일 이름 - * @param sourceCode 파일 내용 + * @param srcDir 소스코드를 저장할 위치가 담긴 객체 + * @param fileName 파일 이름 + * @param content 파일 내용 */ - default void writeToFile(File srcDir, String fileName, String sourceCode) { + default void writeToFile(File srcDir, String fileName, String content) { File file = new File(srcDir, fileName); if (file.exists()) { System.out.printf("%s/%s가 이미 존재합니다. 새롭게 덮어 씌우시겠습니까? (y, n): ", srcDir.getAbsoluteFile(), @@ -50,7 +52,7 @@ default void writeToFile(File srcDir, String fileName, String sourceCode) { } try (FileWriter fileWriter = new FileWriter(file)) { - fileWriter.write(sourceCode); + fileWriter.write(content); } catch (IOException e) { System.out.println("파일 생성 실패. 프로그램을 종료합니다."); throw new RuntimeException(e); @@ -66,7 +68,7 @@ default void writeToFile(File srcDir, String fileName, String sourceCode) { */ static String readFileFromResource(String filePath) throws IOException { StringBuilder sourceCode = new StringBuilder(); - try (InputStream inputStream = SourceCodeTemplateImpl.class.getClassLoader() + try (InputStream inputStream = JavaTemplate.class.getClassLoader() .getResourceAsStream(filePath); InputStreamReader inputStreamReader = new InputStreamReader( Objects.requireNonNull(inputStream)); @@ -80,5 +82,4 @@ static String readFileFromResource(String filePath) throws IOException { } return sourceCode.toString(); } - } diff --git a/src/main/java/kr/huni/problem_parser/BaekjoonProblemParser.java b/src/main/java/kr/huni/problem_parser/BaekjoonProblemParser.java index f0162d6..b3e75cd 100644 --- a/src/main/java/kr/huni/problem_parser/BaekjoonProblemParser.java +++ b/src/main/java/kr/huni/problem_parser/BaekjoonProblemParser.java @@ -7,10 +7,11 @@ */ public class BaekjoonProblemParser { - static final String PROBLEM_URL = "https://www.acmicpc.net/problem/"; + public static final String PROBLEM_URL = "https://www.acmicpc.net/problem/"; static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"; static final String PROBLEM_TITLE_SELECTOR = "span#problem_title"; + static final String PROBLEM_DESCRIPTION_SELECTOR = "div#problem_description"; static final String PROBLEM_INPUT_SELECTOR = "pre[id^=sample-input]"; static final String PROBLEM_OUTPUT_SELECTOR = "pre[id^=sample-output]"; private final WebParser webParser; @@ -31,18 +32,24 @@ public BaekjoonProblemParser(WebParser webParser) { * @return Problem 객체 */ public Problem parse(int problemNumber) { - String title = webParser.parse(PROBLEM_TITLE_SELECTOR)[0]; + String title = webParser.parse(PROBLEM_TITLE_SELECTOR, false)[0]; + String description = webParser.parse(PROBLEM_DESCRIPTION_SELECTOR, true)[0]; final ArrayList testCases = new ArrayList<>(); - String[] inputs = webParser.parse(PROBLEM_INPUT_SELECTOR); - String[] outputs = webParser.parse(PROBLEM_OUTPUT_SELECTOR); + String[] inputs = webParser.parse(PROBLEM_INPUT_SELECTOR, false); + String[] outputs = webParser.parse(PROBLEM_OUTPUT_SELECTOR, false); assert inputs.length == outputs.length; for (int i = 0; i < inputs.length; i++) { testCases.add(new TestCase(inputs[i], outputs[i])); } - return new Problem(problemNumber, title, testCases); + return Problem.builder() + .title(title) + .description(description) + .number(problemNumber) + .testCases(testCases) + .build(); } } diff --git a/src/main/java/kr/huni/problem_parser/JsoupWebParser.java b/src/main/java/kr/huni/problem_parser/JsoupWebParser.java index afd77ac..6edf1a8 100644 --- a/src/main/java/kr/huni/problem_parser/JsoupWebParser.java +++ b/src/main/java/kr/huni/problem_parser/JsoupWebParser.java @@ -29,12 +29,16 @@ public JsoupWebParser(int problemNumber) { } @Override - public String[] parse(String selector) { + public String[] parse(String selector, boolean includeHtml) { Elements result = this.document.select(selector); String[] arr = new String[result.size()]; for (int i = 0; i < result.size(); i++) { - arr[i] = result.get(i).text(); + if (includeHtml) { + arr[i] = result.get(i).html(); + } else { + arr[i] = result.get(i).text(); + } } return arr; diff --git a/src/main/java/kr/huni/problem_parser/Problem.java b/src/main/java/kr/huni/problem_parser/Problem.java index de13896..fd72066 100644 --- a/src/main/java/kr/huni/problem_parser/Problem.java +++ b/src/main/java/kr/huni/problem_parser/Problem.java @@ -3,6 +3,7 @@ import java.util.List; import kr.huni.user_configuration.UserConfiguration; import kr.huni.user_configuration.UserConfigurationLoader; +import lombok.Builder; import lombok.Getter; @Getter @@ -10,16 +11,23 @@ public class Problem { private final int number; private final String title; + private final String description; private final String sourceRootDirectory; private final List testCases; - public Problem(int number, String title, List testCases) { + @Builder + public Problem(int number, String title, String description, List testCases) { UserConfiguration configuration = UserConfigurationLoader.getInstance(); this.number = number; this.title = title; this.sourceRootDirectory = configuration.srcDirPrefix.getValue() + number; + this.description = description; this.testCases = testCases; } + public boolean isExist() { + return (this.number != 0 && this.title != null); + } + } diff --git a/src/main/java/kr/huni/problem_parser/WebParser.java b/src/main/java/kr/huni/problem_parser/WebParser.java index 01da8fb..ad34579 100644 --- a/src/main/java/kr/huni/problem_parser/WebParser.java +++ b/src/main/java/kr/huni/problem_parser/WebParser.java @@ -3,10 +3,11 @@ public interface WebParser { /** - * @param selector 파싱할 selector + * @param selector 파싱할 selector + * @param includeHtml html 태그를 포함할지 여부 * @return 파싱된 String[] * @implSpec 주어진 selector를 파싱하여 String[]로 반환한다 */ - String[] parse(String selector); + String[] parse(String selector, boolean includeHtml); } diff --git a/src/main/java/kr/huni/user_configuration/UserConfiguration.java b/src/main/java/kr/huni/user_configuration/UserConfiguration.java index 77d92be..3d331e2 100644 --- a/src/main/java/kr/huni/user_configuration/UserConfiguration.java +++ b/src/main/java/kr/huni/user_configuration/UserConfiguration.java @@ -1,6 +1,6 @@ package kr.huni.user_configuration; -import kr.huni.code_generator.SourceCodeTemplateImpl; +import kr.huni.code_generator.JavaTemplate; import lombok.extern.slf4j.Slf4j; /** @@ -22,7 +22,21 @@ public class UserConfiguration { .description(""" Main.java 파일의 템플릿입니다. 예악어 {{number}}와 {{title}}을 사용하면 문제번호와 문제제목으로 자동 치환됩니다. """) - .defaultValue(SourceCodeTemplateImpl.DEFAULT_MAIN_CODE_TEMPLATE) + .defaultValue(JavaTemplate.DEFAULT_MAIN_CODE_TEMPLATE) + .build(); + public final UserConfigurationField markdownTemplate = + UserConfigurationField.builder() + .description(""" + 문제 설명을 저장할 마크다운 파일의 템플릿입니다. 예악어 {{title}}, {{problem_number}}, {{description}}, {{source}}를 사용하면 문제제목, 문제번호, 문제설명, 출처로 자동 치환됩니다. + """) + .defaultValue(JavaTemplate.DEFAULT_MARKDOWN_TEMPLATE) + .build(); + public final UserConfigurationField enableReadme = + UserConfigurationField.builder() + .description(""" + README.md 파일을 생성할지 여부를 결정합니다. + """) + .defaultValue("true") .build(); public static UserConfiguration defaultConfiguration() { @@ -34,17 +48,15 @@ public static UserConfiguration defaultConfiguration() { */ void printValue() { log.info(""" - 설정 정보를 출력합니다. - srcDirPrefix : {} - srcCommentFormat : {} - """, srcDirPrefix.getValue(), mainCodeTemplate.getValue()); + 설정 정보를 출력합니다. + srcDirPrefix : {} + mainCodeTemplate : {} + markdownTemplate : {} + enableReadme : {} + """, srcDirPrefix.getValue(), mainCodeTemplate.getValue(), markdownTemplate.getValue(), + enableReadme.getValue()); } protected UserConfiguration() { } - - public void merge(UserConfiguration userConfiguration) { - this.srcDirPrefix.setValue(userConfiguration.srcDirPrefix.getValue()); - this.mainCodeTemplate.setValue(userConfiguration.mainCodeTemplate.getValue()); - } } diff --git a/src/main/java/kr/huni/user_configuration/UserConfigurationLoader.java b/src/main/java/kr/huni/user_configuration/UserConfigurationLoader.java index 91c8ec2..006886a 100644 --- a/src/main/java/kr/huni/user_configuration/UserConfigurationLoader.java +++ b/src/main/java/kr/huni/user_configuration/UserConfigurationLoader.java @@ -37,7 +37,12 @@ private static UserConfiguration loadProperties() { log.info("설정 파일을 읽어옵니다."); ObjectMapper objectMapper = new ObjectMapper(); - configuration.merge(objectMapper.readValue(configFile, UserConfiguration.class)); + // read only exist field + configuration = objectMapper.readerForUpdating(configuration).readValue(configFile); + + // and rewrite all fields + String jsonString = objectMapper.writeValueAsString(configuration); + writeStringToFile(jsonString); } else { log.info("설정 파일이 존재하지 않습니다. 설정 파일을 새로 생성합니다."); createDefaultConfigurationFile(); diff --git a/src/test/java/kr/huni/Integration/IntegrationTests.java b/src/test/java/kr/huni/Integration/IntegrationTests.java index 0f6283f..d24d08e 100644 --- a/src/test/java/kr/huni/Integration/IntegrationTests.java +++ b/src/test/java/kr/huni/Integration/IntegrationTests.java @@ -20,12 +20,12 @@ class IntegrationTests { @BeforeEach - void setUp() throws IOException { + void setUp() throws IOException, NoSuchFieldException, IllegalAccessException { TestCleaner.clean(); } @AfterEach - void tearDown() throws IOException { + void tearDown() throws IOException, NoSuchFieldException, IllegalAccessException { TestCleaner.clean(); } @@ -45,4 +45,21 @@ void applicationTest() { // then assertTrue(new File("p1000/src/Main.java").exists()); } + + @Test + @DisplayName("프로그램에서 README.md 파일이 잘 생성된다.") + void Readme_generate_well() { + // given + BojStarter program = new BojStarter( + new FakeCodeOpen(), + new JavaSourceCodeFile(), + new JavaCodeGenerator(), + new BaekjoonProblemParser(new JsoupWebParser(1000))); + + // when + program.run(1000); + + // then + assertTrue(new File("p1000/src/README.md").exists()); + } } diff --git a/src/test/java/kr/huni/TestCleaner.java b/src/test/java/kr/huni/TestCleaner.java index 48ba9ad..9fac4d7 100644 --- a/src/test/java/kr/huni/TestCleaner.java +++ b/src/test/java/kr/huni/TestCleaner.java @@ -4,29 +4,41 @@ import java.io.File; import java.io.IOException; +import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Path; +import kr.huni.user_configuration.UserConfigurationLoader; public class TestCleaner { - static public void clean() throws IOException { + static public void clean() throws IOException, NoSuchFieldException, IllegalAccessException { System.out.println("Clean up..."); // 설정 파일 삭제 - File configFile = new File(CONFIGURATION_FILE_NAME); - if (configFile.exists()) { - Files.deleteIfExists(Path.of(CONFIGURATION_FILE_NAME)); - assert !new File(CONFIGURATION_FILE_NAME).exists(); - } + clearConfigurationFile(); // 생성된 파일 삭제 Files.deleteIfExists(Path.of("p1000/src/Main.java")); Files.deleteIfExists(Path.of("p1000/src/TestHelper.java")); Files.deleteIfExists(Path.of("p1000/src/NoTestHelper.java")); + Files.deleteIfExists(Path.of("p1000/src/README.md")); Files.deleteIfExists(Path.of("p1000/src")); Files.deleteIfExists(Path.of("p1000")); assert !new File("p1000").exists(); } + private static void clearConfigurationFile() + throws IOException, NoSuchFieldException, IllegalAccessException { + File configFile = new File(CONFIGURATION_FILE_NAME); + if (configFile.exists()) { + Files.deleteIfExists(Path.of(CONFIGURATION_FILE_NAME)); + assert !new File(CONFIGURATION_FILE_NAME).exists(); + } + + Field instance = UserConfigurationLoader.class.getDeclaredField("config"); + instance.setAccessible(true); + instance.set(null, null); + } + } diff --git a/src/test/java/kr/huni/code_generator/CodeGeneratorTests.java b/src/test/java/kr/huni/code_generator/CodeGeneratorTests.java index a8708c7..f7f0423 100644 --- a/src/test/java/kr/huni/code_generator/CodeGeneratorTests.java +++ b/src/test/java/kr/huni/code_generator/CodeGeneratorTests.java @@ -14,10 +14,11 @@ class CodeGeneratorTests { void javaCodeGenerator_return_code() { // given JavaCodeGenerator javaCodeGenerator = new JavaCodeGenerator(); - Problem problem = new Problem(-1, "A+B", new ArrayList<>()); + Problem problem = new Problem(-1, "A+B", "두 정수 A와 B를 입력받은 다음, A+B를 출력하는 프로그램을 작성하시오.", + new ArrayList<>()); // when - GeneratedCode generatedCode = javaCodeGenerator.generate(problem); + GeneratedCode generatedCode = javaCodeGenerator.generateCode(problem); // then Assertions.assertAll( diff --git a/src/test/java/kr/huni/code_generator/FakeCodeGenerator.java b/src/test/java/kr/huni/code_generator/FakeCodeGenerator.java index e89a84e..4e5db0d 100644 --- a/src/test/java/kr/huni/code_generator/FakeCodeGenerator.java +++ b/src/test/java/kr/huni/code_generator/FakeCodeGenerator.java @@ -2,10 +2,17 @@ import kr.huni.problem_parser.Problem; -public class FakeCodeGenerator implements CodeGenerator { +public class FakeCodeGenerator implements FileContentGenerator { @Override - public GeneratedCode generate(Problem problem) { + public GeneratedCode generateCode(Problem problem) { return new GeneratedCode("mainCode", "testCode"); } + + @Override + public String generateMarkdown(Problem problem) { + return """ + # 1. A+B + """; + } } diff --git a/src/test/java/kr/huni/code_generator/SourceCodeTemplateTests.java b/src/test/java/kr/huni/code_generator/SourceCodeTemplateTests.java index 8edeb8d..bbcf2ab 100644 --- a/src/test/java/kr/huni/code_generator/SourceCodeTemplateTests.java +++ b/src/test/java/kr/huni/code_generator/SourceCodeTemplateTests.java @@ -16,7 +16,7 @@ class SourceCodeTemplateTests { @DisplayName("생성되는 기본 Main.java 기본 템플릿은 문법적으로 오류가 없다.") void main_syntax_fine() throws IOException { // given - SourceCodeTemplateImpl sourceCodeTemplate = new SourceCodeTemplateImpl(); + JavaTemplate sourceCodeTemplate = new JavaTemplate(); String mainSourceCode = sourceCodeTemplate.getMainCode(1000, "A+B"); // when @@ -33,7 +33,7 @@ void test_syntax_fine() throws IOException { ArrayList testCases = new ArrayList<>(); testCases.add(new TestCase("1 2", "3")); - SourceCodeTemplateImpl sourceCodeTemplate = new SourceCodeTemplateImpl(); + JavaTemplate sourceCodeTemplate = new JavaTemplate(); String testCode = sourceCodeTemplate.getTestCode(testCases); testCode += """ class Main { @@ -54,7 +54,7 @@ public static void main(String[] args) { @DisplayName("테스트 케이스가 없을때 생성된 NoTestHelper.java는 문법적으로 오류가 없다.") void test_syntax_fine_with_no_case() throws IOException { // given - SourceCodeTemplateImpl sourceCodeTemplate = new SourceCodeTemplateImpl(); + JavaTemplate sourceCodeTemplate = new JavaTemplate(); String testCode = sourceCodeTemplate.getTestCode(new ArrayList<>()); // when @@ -68,7 +68,7 @@ void test_syntax_fine_with_no_case() throws IOException { @DisplayName("테스트케이스가 없을때 생성된 NoTestHelper.java 는 고정된 코드 문자를 반환한다.") void noTestHelper_load_well() throws IOException { // given - SourceCodeTemplateImpl sourceCodeTemplate = new SourceCodeTemplateImpl(); + JavaTemplate sourceCodeTemplate = new JavaTemplate(); String noTestHelperCode = sourceCodeTemplate.getTestCode(new ArrayList<>()); // when & then @@ -89,8 +89,8 @@ public static void main() { @DisplayName("TestHelper.java 코드 템플릿에 치환 문자가 존재 한다.") void test_replace_text_exist() throws IOException { // given - String codePath = SourceCodeTemplateImpl.TEST_JAVA_FILE; - String replacedTestCaseSymbol = SourceCodeTemplateImpl.REPLACED_TEST_CASES; + String codePath = JavaTemplate.TEST_JAVA_FILE; + String replacedTestCaseSymbol = JavaTemplate.REPLACED_TEST_CASES; // when String sourceCode = SourceCodeFile.readFileFromResource(codePath); diff --git a/src/test/java/kr/huni/file_generator/JavaSourceCodeFileTests.java b/src/test/java/kr/huni/file_generator/JavaSourceCodeFileTests.java index 11412e3..e7ab5ad 100644 --- a/src/test/java/kr/huni/file_generator/JavaSourceCodeFileTests.java +++ b/src/test/java/kr/huni/file_generator/JavaSourceCodeFileTests.java @@ -14,17 +14,17 @@ class JavaSourceCodeFileTests { @BeforeEach - void setUp() throws IOException { + void setUp() throws IOException, NoSuchFieldException, IllegalAccessException { TestCleaner.clean(); } @AfterEach - void tearDown() throws IOException { + void tearDown() throws IOException, NoSuchFieldException, IllegalAccessException { TestCleaner.clean(); } @Test - @DisplayName("이미 존재하는 파일을 생성하려고 할때 y 입력시 파일이 덮어씌워진다.") + @DisplayName("이미 존재하는 소스코드 파일을 생성하려고 할때 y 입력시 파일이 덮어씌워진다.") void writeToFile() throws IOException { // given String sourceRootDirectory = "p1000"; @@ -32,7 +32,7 @@ void writeToFile() throws IOException { String overWrittenSourceCode = "over written source code"; JavaSourceCodeFile javaSourceCodeFile = new JavaSourceCodeFile(); - javaSourceCodeFile.write(sourceRootDirectory, fileName, overWrittenSourceCode); + javaSourceCodeFile.write(sourceRootDirectory, fileName, overWrittenSourceCode, "", false); File srcDir = new File(sourceRootDirectory, "src"); // when diff --git a/src/test/java/kr/huni/problem_parser/ProblemTests.java b/src/test/java/kr/huni/problem_parser/ProblemTests.java index 5de40fb..25a0907 100644 --- a/src/test/java/kr/huni/problem_parser/ProblemTests.java +++ b/src/test/java/kr/huni/problem_parser/ProblemTests.java @@ -13,7 +13,8 @@ class ProblemTests { void problem_dir_prefix_test() { // given & when int problemId = 1000; - Problem problem = new Problem(problemId, "A+B", null); + Problem problem = new Problem(problemId, "A+B", "두 정수 A와 B를 입력받은 다음, A+B를 출력하는 프로그램을 작성하시오.", + null); String srcDirPrefix = UserConfigurationLoader.getInstance().srcDirPrefix.getValue(); String expected = srcDirPrefix + problemId; diff --git a/src/test/java/kr/huni/problem_parser/WebParserStub.java b/src/test/java/kr/huni/problem_parser/WebParserStub.java index ae52396..5908df1 100644 --- a/src/test/java/kr/huni/problem_parser/WebParserStub.java +++ b/src/test/java/kr/huni/problem_parser/WebParserStub.java @@ -3,7 +3,7 @@ public class WebParserStub implements WebParser { @Override - public String[] parse(String selector) { + public String[] parse(String selector, boolean includeHtml) { return new String[]{"String1", "String2"}; } diff --git a/src/test/java/kr/huni/user_configuration/UserConfigurationLoaderTest.java b/src/test/java/kr/huni/user_configuration/UserConfigurationLoaderTest.java index 32e7ec4..598fa4a 100644 --- a/src/test/java/kr/huni/user_configuration/UserConfigurationLoaderTest.java +++ b/src/test/java/kr/huni/user_configuration/UserConfigurationLoaderTest.java @@ -1,12 +1,28 @@ package kr.huni.user_configuration; +import static kr.huni.user_configuration.UserConfigurationLoader.CONFIGURATION_FILE_NAME; import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import kr.huni.TestCleaner; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; class UserConfigurationLoaderTest { + @BeforeEach + void setUp() throws IOException, NoSuchFieldException, IllegalAccessException { + TestCleaner.clean(); + } + @Test @DisplayName("ConfigurationLoader 인스턴스를 두 번 생성하면 같은 인스턴스를 반환한다.") void singleton_test() { @@ -17,4 +33,22 @@ void singleton_test() { // when & then assertSame(firstInstance, secondInstance); } + + @Test + @DisplayName("getInstance 호출시 설정 파일에 없는 필드는 기본 값으로 덮어 씌워진다.") + void config_overwrite_work() throws IOException { + // given + Map configMap = new HashMap<>(); + configMap.put("srcDirPrefix", Collections.singletonMap("value", "p")); + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.writeValue(new File(CONFIGURATION_FILE_NAME), configMap); + + // when + UserConfigurationLoader.getInstance(); + + // then + File file = new File(CONFIGURATION_FILE_NAME); + String content = new String(Files.readAllBytes(file.toPath())); + assertTrue(content.contains("mainCodeTemplate")); + } } \ No newline at end of file diff --git a/src/test/java/kr/huni/user_configuration/UserConfigurationTest.java b/src/test/java/kr/huni/user_configuration/UserConfigurationTest.java index 8c8562f..f067b1e 100644 --- a/src/test/java/kr/huni/user_configuration/UserConfigurationTest.java +++ b/src/test/java/kr/huni/user_configuration/UserConfigurationTest.java @@ -15,27 +15,15 @@ void defaultConfiguration() { UserConfiguration defaultConfiguration = UserConfiguration.defaultConfiguration(); // then - Assertions.assertSame(defaultConfiguration.mainCodeTemplate.getDefaultValue(), - UserConfiguration.defaultConfiguration().mainCodeTemplate.getValue()); - Assertions.assertSame(defaultConfiguration.srcDirPrefix.getDefaultValue(), - UserConfiguration.defaultConfiguration().srcDirPrefix.getValue()); - } - - @Test - @DisplayName("merge() 메서드는 인자로 받은 Configuration 객체의 value 값을 병합한다.") - void merge() { - // given - UserConfiguration userConfiguration = UserConfiguration.defaultConfiguration(); - UserConfiguration userConfiguration2 = UserConfiguration.defaultConfiguration(); - userConfiguration.srcDirPrefix.setValue("1"); - userConfiguration2.srcDirPrefix.setValue("2"); - - // when - userConfiguration.merge(userConfiguration2); - - // then - Assertions.assertSame("2", userConfiguration.srcDirPrefix.getValue()); - + Assertions.assertAll( + () -> Assertions.assertSame(defaultConfiguration.mainCodeTemplate.getDefaultValue(), + UserConfiguration.defaultConfiguration().mainCodeTemplate.getValue()), + () -> Assertions.assertSame(defaultConfiguration.srcDirPrefix.getDefaultValue(), + UserConfiguration.defaultConfiguration().srcDirPrefix.getValue()), + () -> Assertions.assertSame(defaultConfiguration.markdownTemplate.getDefaultValue(), + UserConfiguration.defaultConfiguration().markdownTemplate.getValue()), + () -> Assertions.assertSame("true", defaultConfiguration.enableReadme.getValue()) + ); } @Test