diff --git a/architecture.md b/architecture.md index 34cfe8d..32adb09 100644 --- a/architecture.md +++ b/architecture.md @@ -38,13 +38,31 @@ went fine) A notification contains only the repo and the issue where the bot has been mentioned. To find the command, the bot searches for the **last** (most recent) comment in the issue where it's been mentioned. Only the last mentioning comment will be treated as a command, in order to avoid spamming. -The bot has a [Brain](https://github.com/opencharles/charles-rest/blob/master/src/main/java/com/amihaiemil/charles/github/Brain.java) and at the beginning of each Action, it tries to **understand** the command. -Understanding a command means building up the [Step](https://github.com/opencharles/charles-rest/blob/master/src/main/java/com/amihaiemil/charles/github/Step.java)s to be executed in order to fulfill the command. - -If you look inside class Brain you will see that Steps are instantiated one on top of the other, like an umbrella. Basically, for each command a tree-like structure of steps is built. - -Please note that currently class **Brain** has grown quite big and needs to be refactored and broken down in smaller pieces. -There is an issue for that [here](https://github.com/opencharles/charles-rest/issues/155). +The bot has a set of knowledges (implementations of Knowledge), which it uses in order to try and "understand" a command. +Understanding a command means building the tree of steps which are to be executed in order to fulfil the command. + +First, the bot puts together all its knowledges, then it uses them to find the proper steps. The last Knowledge in the chain is Confused, which means +the bot didn't manage to categorize the command and it will leave a Github reply informing the commander about the sitiuation: + +```java + final Knowledge knowledge = new Conversation(//it can have a convesation + new Hello(//it can say hello + new IndexSiteKn(//it can index a site + this.logs, + new IndexSitemapKn(//it can index a site via a sitemap.xml + this.logs, + new IndexPageKn(//it can index a single page + this.logs, + new DeleteIndexKn(//it can delete the idnex + this.logs, + new Confused()//it can be confused (not understand the command) + ) + ) + ) + ) + ) + ) +``` ## Steps diff --git a/src/main/java/com/amihaiemil/charles/github/Action.java b/src/main/java/com/amihaiemil/charles/github/Action.java index 5d3b2cb..a47501d 100644 --- a/src/main/java/com/amihaiemil/charles/github/Action.java +++ b/src/main/java/com/amihaiemil/charles/github/Action.java @@ -62,11 +62,6 @@ public class Action { */ private Issue issue; - /** - * Brain of the github agent. - */ - private Brain br; - /** * Location of the logs. */ @@ -85,24 +80,54 @@ public Action(Issue issue) throws IOException { this.logs = new LogsOnServer( System.getProperty("charles.rest.logs.endpoint"), this.id + ".log" ); - this.br = new Brain(this.logger, this.logs); } public void perform() { ValidCommand command; try { - logger.info("Started action " + this.id); - LastComment lc = new LastComment(issue); + this.logger.info("Started action " + this.id); + final LastComment lc = new LastComment(issue); command = new ValidCommand(lc); String commandBody = command.json().getString("body"); - logger.info("Received command: " + commandBody); - Steps steps = br.understand(command); + this.logger.info("Received command: " + commandBody); + + final Knowledge knowledge = new Conversation( + new Hello( + new IndexSiteKn( + this.logs, + new IndexSitemapKn( + this.logs, + new IndexPageKn( + this.logs, + new DeleteIndexKn( + this.logs, + new Confused() + ) + ) + ) + ) + ) + ); + + final Steps steps = new Steps( + knowledge.handle(command), + new SendReply( + new TextReply( + command, + String.format( + command.language().response("step.failure.comment"), + command.authorLogin(), this.logs.address() + ) + ), + new Step.FinalStep("[ERROR] Some step didn't execute properly.") + ) + ); steps.perform(command, logger); - } catch (IllegalArgumentException e) { - logger.warn("No command found in the issue or the agent has already replied to the last command!"); - } catch (IOException e) { - logger.error("Action failed entirely with exception: ", e); + } catch (final IllegalArgumentException e) { + this.logger.warn("No command found in the issue or the agent has already replied to the last command!"); + } catch (final IOException e) { + this.logger.error("Action failed entirely with exception: ", e); this.sendReply( new ErrorReply(logs.address(), this.issue) ); @@ -117,7 +142,7 @@ private void sendReply(Reply reply) { try { reply.send(); } catch (IOException e) { - logger.error("FAILED TO REPLY!", e); + this.logger.error("FAILED TO REPLY!", e); } } diff --git a/src/main/java/com/amihaiemil/charles/github/Brain.java b/src/main/java/com/amihaiemil/charles/github/Brain.java deleted file mode 100644 index aa0bde1..0000000 --- a/src/main/java/com/amihaiemil/charles/github/Brain.java +++ /dev/null @@ -1,353 +0,0 @@ -/** - * Copyright (c) 2016-2017, Mihai Emil Andronache - * All rights reserved. - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * 1)Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2)Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3)Neither the name of charles-rest nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package com.amihaiemil.charles.github; - -import java.io.IOException; - -import org.slf4j.Logger; - -/** - * The "brain" of the Github agent. Can understand commands and - * figure out the Steps that need to be performed to fulfill the - * command. - * @author Mihai Andronache (amihaiemil@gmail.com) - * @version $Id$ - * @since 1.0.0 - * - */ -public class Brain { - - /** - * All the languages that the chatbot can understang/speaks. - */ - private Language[] languages; - - /** - * The action's logger. - */ - private Logger logger; - - /** - * Location of the log file. - */ - private LogsLocation logsLoc; - - /** - * Constructor. - */ - public Brain(Logger logger, LogsLocation logsLoc) { - this(logger, logsLoc, new English()); - } - - /** - * Constructor which takes the responses and languages. - * @param resp - * @param langs - */ - public Brain(Logger logger, LogsLocation logsLoc, Language... langs) { - this.logger = logger; - this.logsLoc = logsLoc; - this.languages = langs; - } - - /** - * Understand a command. - * @param com Given command. - * @return Steps. - * @throws IOException if something goes worng. - */ - public Steps understand(Command com) throws IOException { - String authorLogin = com.authorLogin(); - logger.info("Command author's login: " + authorLogin); - Step steps; - String category = this.categorizeCommand(com); - switch (category) { - case "hello": - String hello = String.format(new English().response("hello.comment"), authorLogin); - steps = new SendReply( - new TextReply(com, hello), new Step.FinalStep() - ); - break; - case "indexsite": - steps = this.withCommonChecks( - com, new English(), this.indexSiteStep(com,new English()) - ); - break; - case "indexpage": - steps = new PageHostedOnGithubCheck( - this.withCommonChecks( - com, new English(), this.indexPageStep(com, new English()) - ), - this.finalCommentStep(com, new English(), "denied.badlink.comment", com.authorLogin()) - ); - break; - case "indexsitemap": - steps = new PageHostedOnGithubCheck( - this.withCommonChecks( - com, new English(), - this.indexSitemapStep(com, new English()) - ), - this.finalCommentStep( - com, new English(), - "denied.badlink.comment", - com.authorLogin() - ) - ); - break; - case "deleteindex": - steps = new DeleteIndexCommandCheck( - new IndexExistsCheck( - com.indexName(), - new AuthorOwnerCheck( - this.deleteIndexStep(com, new English()), - new OrganizationAdminCheck( - this.deleteIndexStep(com, new English()), - this.finalCommentStep( - com, new English(), - "denied.commander.comment", com.authorLogin() - ) - ) - ), - this.finalCommentStep( - com, new English(), "index.missing.comment", - com.authorLogin(), - this.logsLoc.address() - ) - ), - this.finalCommentStep( - com, new English(), "denied.deleteindex.comment", - com.authorLogin(), com.agentLogin(), com.repo().name() - ) - ); - break; - default: - logger.info("Unknwon command!"); - String unknown = String.format( - new English().response("unknown.comment"), - authorLogin); - steps = new SendReply( - new TextReply(com, unknown), - new Step.FinalStep() - ); - break; - } - return new Steps( - steps, - new SendReply( - new TextReply( - com, - String.format( - new English().response("step.failure.comment"), - com.authorLogin(), this.logsLoc.address() - ) - ), - new Step.FinalStep("[ERROR] Some step didn't execute properly.") - ) - ); - } - - /** - * Steps for indexpage step. - * @param com Command - * @param lang Language - * @return Step - * @throws IOException - */ - public Step indexPageStep(Command com, Language lang) throws IOException { - return new SendReply( - new TextReply( - com, - String.format( - lang.response("index.start.comment"), - com.authorLogin(), - this.logsLoc.address() - ) - ), - new IndexPage( - new StarRepo( - this.finalCommentStep( - com, lang, "index.finished.comment", - com.authorLogin(), - this.logsLoc.address() - ) - ) - ) - ); - } - - /** - * Steps for indexsitemap step. - * @param com Command - * @param lang Language - * @return Step - * @throws IOException - */ - public Step indexSitemapStep(Command com, Language lang) throws IOException { - return new SendReply( - new TextReply( - com, - String.format( - lang.response("index.start.comment"), - com.authorLogin(), - this.logsLoc.address() - ) - ), - new IndexSitemap( - new StarRepo( - this.finalCommentStep( - com, lang, "index.finished.comment", - com.authorLogin(), - this.logsLoc.address() - ) - ) - ) - ); - } - - /** - * Steps for indexsite action - * @param com Command - * @param lang Language - * @return Step - * @throws IOException - */ - public Step indexSiteStep(Command com, Language lang) throws IOException { - return new SendReply( - new TextReply( - com, - String.format( - lang.response("index.start.comment"), - com.authorLogin(), - this.logsLoc.address() - ) - ), - new IndexSite( - new StarRepo( - this.finalCommentStep( - com, lang, "index.finished.comment", - com.authorLogin(), - this.logsLoc.address() - ) - ) - ) - ); - } - - /** - * Steps for deleteindex action. - * @param com Command - * @param lang Language - * @return Step - * @throws IOException - */ - public Step deleteIndexStep(Command com, Language lang) throws IOException { - return new DeleteIndex( - this.finalCommentStep( - com, lang, "deleteindex.finished.comment", - com.authorLogin(), - com.repo().name(), - this.logsLoc.address() - ) - ); - } - - /** - * Find out the type and Language of a command. - * @param com Received Command. - * @param logger Logger to use. - * @return CommandCategory, which defaults to unknown command and - * first language in the agent's languages list (this.languages) - * @throws IOException - */ - private String categorizeCommand(Command com) throws IOException { - String category = "unknown"; - for(Language lang : this.languages) { - category = lang.categorize(com); - if(!"unknown".equals(category)) { - this.logger.info( - "Command type: " + category + - ". Language: " + lang.getClass().getSimpleName() - ); - break; - } - } - return category; - } - - /** - * Add precondition checks that are common to most actions (indexsite, indexpage, deleteindex etc) - * @param com Received Command, - * @param lang Spoken language. - * @param action Step that should be performed after the checks are met. - * @return Steps that have to be followed to fulfill an index command. - * @throws IOException If something goes wrong. - */ - private Step withCommonChecks(Command com, Language lang, Step action) throws IOException { - PreconditionCheckStep repoForkCheck = new RepoForkCheck( - action, - this.finalCommentStep(com, lang, "denied.fork.comment", com.authorLogin()) - ); - PreconditionCheckStep authorOwnerCheck = new AuthorOwnerCheck( - repoForkCheck, - new OrganizationAdminCheck( - repoForkCheck, - this.finalCommentStep(com, lang, "denied.commander.comment", com.authorLogin()) - ) - ); - PreconditionCheckStep repoNameCheck = new RepoNameCheck( - authorOwnerCheck, - new GhPagesBranchCheck( - authorOwnerCheck, - this.finalCommentStep(com, lang, "denied.name.comment", com.authorLogin()) - ) - ); - return repoNameCheck; - } - - /** - * Builds the final comment to be sent to the issue. This should be the last in the steps' chain. - * @param com Command. - * @param lang Spoken language. - * @param formatParts Parts to format the response %s elements with. - * @return SendReply step. - */ - private SendReply finalCommentStep( - Command com, Language lang, String messagekey, String ... formatParts - ) { - return new SendReply( - new TextReply( - com, - String.format( - lang.response(messagekey), - (Object[]) formatParts - ) - ), - new Step.FinalStep() - ); - } - -} diff --git a/src/main/java/com/amihaiemil/charles/github/Confused.java b/src/main/java/com/amihaiemil/charles/github/Confused.java new file mode 100644 index 0000000..d93b484 --- /dev/null +++ b/src/main/java/com/amihaiemil/charles/github/Confused.java @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2016-2017, Mihai Emil Andronache + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1)Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2)Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3)Neither the name of charles-rest nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.amihaiemil.charles.github; + +import java.io.IOException; + +/** + * The bot didn't understand the command, so he has to leave a comment informing the commander + * of its confusion. + * @author Mihai Andronache (amihaiemil@gmail.com) + * @version $Id$ + * @since 1.0.1 + */ +public class Confused implements Knowledge { + + @Override + public Step handle(Command com) throws IOException { + return new SendReply( + new TextReply( + com, + String.format( + com.language().response("unknown.comment"), + com.authorLogin() + ) + ), + new Step.FinalStep() + ); + } + +} diff --git a/src/main/java/com/amihaiemil/charles/github/Conversation.java b/src/main/java/com/amihaiemil/charles/github/Conversation.java index de75367..a82ea93 100644 --- a/src/main/java/com/amihaiemil/charles/github/Conversation.java +++ b/src/main/java/com/amihaiemil/charles/github/Conversation.java @@ -32,8 +32,6 @@ * @author Mihai Andronache (amihaiemil@gmail.com) * @version $Id$ * @since 1.0.1 - * @todo #222:30min Hello was added, to handle hello commands. Continue - * with further Knowledges for the other commands (index site, index page etc). */ public final class Conversation implements Knowledge { diff --git a/src/main/java/com/amihaiemil/charles/github/DeleteIndexKn.java b/src/main/java/com/amihaiemil/charles/github/DeleteIndexKn.java new file mode 100644 index 0000000..f14d83d --- /dev/null +++ b/src/main/java/com/amihaiemil/charles/github/DeleteIndexKn.java @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2016-2017, Mihai Emil Andronache + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1)Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2)Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3)Neither the name of charles-rest nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.amihaiemil.charles.github; + +import java.io.IOException; + +/** + * The bot knows how to delete an index. + * @author Mihai Andronache (amihaiemil@gmail.com) + * @version $Id$ + * @since 1.0.1 + */ +public final class DeleteIndexKn implements Knowledge { + + /** + * Location of the log file. + */ + private LogsLocation logsLoc; + + /** + * What do we do if it's not an 'deleteindex' command? + */ + private Knowledge notDelete; + + /** + * Ctor. + * @param logsLoc Location of the log file for the bot's action. + * @param notDelete What do we do if it's not an 'deleteindex' command? + */ + public DeleteIndexKn(final LogsLocation logsLoc, final Knowledge notDelete) { + this.logsLoc = logsLoc; + this.notDelete = notDelete; + } + + @Override + public Step handle(final Command com) throws IOException { + if("deleteindex".equalsIgnoreCase(com.type())) { + return new DeleteIndexCommandCheck( + new IndexExistsCheck( + com.indexName(), + new GeneralPreconditionsCheck( + new DeleteIndex( + new SendReply( + new TextReply( + com, + String.format( + com.language().response("deleteindex.finished.comment"), + com.authorLogin(), com.repo().name(), this.logsLoc.address() + ) + ), + new Step.FinalStep() + ) + ) + ), + new SendReply( + new TextReply( + com, + String.format( + com.language().response("index.missing.comment"), + com.authorLogin(), this.logsLoc.address() + ) + ), + new Step.FinalStep() + ) + ), + new SendReply( + new TextReply( + com, + String.format( + com.language().response("denied.deleteindex.comment"), + com.authorLogin(), com.agentLogin(), com.repo().name() + ) + ), + new Step.FinalStep() + ) + ); + } + return this.notDelete.handle(com); + } + +} diff --git a/src/main/java/com/amihaiemil/charles/github/Hello.java b/src/main/java/com/amihaiemil/charles/github/Hello.java index a9e46da..58fbbe7 100644 --- a/src/main/java/com/amihaiemil/charles/github/Hello.java +++ b/src/main/java/com/amihaiemil/charles/github/Hello.java @@ -42,7 +42,7 @@ public final class Hello implements Knowledge { /** * Ctor. - * @param notHello Followup of this conversation; what does it know to do next? + * @param notHello What do we do if it's not a 'hello' command? */ public Hello(final Knowledge notHello) { this.notHello = notHello; diff --git a/src/main/java/com/amihaiemil/charles/github/IndexPageKn.java b/src/main/java/com/amihaiemil/charles/github/IndexPageKn.java new file mode 100644 index 0000000..411c01a --- /dev/null +++ b/src/main/java/com/amihaiemil/charles/github/IndexPageKn.java @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2016-2017, Mihai Emil Andronache + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1)Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2)Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3)Neither the name of charles-rest nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.amihaiemil.charles.github; + +import java.io.IOException; + +/** + * The bot knows how to index a single page from the website. + * @author Mihai Andronache (amihaiemil@gmail.com) + * @version $Id$ + * @since 1.0.1 + */ +public final class IndexPageKn implements Knowledge { + + /** + * Location of the log file. + */ + private LogsLocation logsLoc; + + /** + * What do we do if it's not an 'indexpage' command? + */ + private Knowledge notIdxPage; + + /** + * Ctor. + * @param logsLoc Location of the log file for the bot's action. + * @param notIdxPage What do we do if it's not an 'indexpage' command? + */ + public IndexPageKn(final LogsLocation logsLoc, final Knowledge notIdxPage) { + this.logsLoc = logsLoc; + this.notIdxPage = notIdxPage; + } + + @Override + public Step handle(final Command com) throws IOException { + if("indexpage".equalsIgnoreCase(com.type())) { + return new PageHostedOnGithubCheck( + new GeneralPreconditionsCheck( + new SendReply( + new TextReply( + com, + String.format( + com.language().response("index.start.comment"), + com.authorLogin(), + this.logsLoc.address() + ) + ), + new IndexPage( + new StarRepo( + new SendReply( + new TextReply( + com, + String.format( + com.language().response("index.finished.comment"), + com.authorLogin(), this.logsLoc.address() + ) + ), + new Step.FinalStep() + ) + ) + ) + ) + ), + new SendReply( + new TextReply( + com, + String.format( + com.language().response("denied.badlink.comment"), + com.authorLogin() + ) + ), + new Step.FinalStep() + ) + ); + } + return this.notIdxPage.handle(com); + } + +} diff --git a/src/main/java/com/amihaiemil/charles/github/IndexSiteKn.java b/src/main/java/com/amihaiemil/charles/github/IndexSiteKn.java new file mode 100644 index 0000000..f6e3d4d --- /dev/null +++ b/src/main/java/com/amihaiemil/charles/github/IndexSiteKn.java @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2016-2017, Mihai Emil Andronache + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1)Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2)Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3)Neither the name of charles-rest nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.amihaiemil.charles.github; + +import java.io.IOException; + +/** + * The bot knows how to index an entire website. + * @author Mihai Andronache (amihaiemil@gmail.com) + * @version $Id$ + * @since 1.0.1 + */ +public final class IndexSiteKn implements Knowledge { + + /** + * Location of the log file. + */ + private LogsLocation logsLoc; + + /** + * What do we do if it's not an 'indexsite' command? + */ + private Knowledge notIdxSite; + + /** + * Ctor. + * @param logsLoc Location of the log file for the bot's action. + * @param notIdxSite What do we do if it's not an 'indexsite' command? + */ + public IndexSiteKn(final LogsLocation logsLoc, final Knowledge notIdxSite) { + this.logsLoc = logsLoc; + this.notIdxSite = notIdxSite; + } + + @Override + public Step handle(final Command com) throws IOException { + if("indexsite".equalsIgnoreCase(com.type())) { + return new GeneralPreconditionsCheck( + new SendReply( + new TextReply( + com, + String.format( + com.language().response("index.start.comment"), + com.authorLogin(), + this.logsLoc.address() + ) + ), + new IndexSite( + new StarRepo( + new SendReply( + new TextReply( + com, + String.format( + com.language().response("index.finished.comment"), + com.authorLogin(), this.logsLoc.address() + ) + ), + new Step.FinalStep() + ) + ) + ) + ) + ); + } + return this.notIdxSite.handle(com); + } + +} diff --git a/src/main/java/com/amihaiemil/charles/github/IndexSitemapKn.java b/src/main/java/com/amihaiemil/charles/github/IndexSitemapKn.java new file mode 100644 index 0000000..85a730f --- /dev/null +++ b/src/main/java/com/amihaiemil/charles/github/IndexSitemapKn.java @@ -0,0 +1,101 @@ +/** + * Copyright (c) 2016-2017, Mihai Emil Andronache + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1)Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2)Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3)Neither the name of charles-rest nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.amihaiemil.charles.github; + +import java.io.IOException; + +/** + * The bot knows how to index a website based on its sitemap.xml + * @author Mihai Andronache (amihaiemil@gmail.com) + * @version $Id$ + * @since 1.0.1 + */ +public final class IndexSitemapKn implements Knowledge { + + /** + * Location of the log file. + */ + private LogsLocation logsLoc; + + /** + * What do we do if it's not an 'indexsitemap' command? + */ + private Knowledge notIdxSitemap; + + /** + * Ctor. + * @param logsLoc Location of the log file for the bot's action. + * @param notIdxSitemap What do we do if it's not an 'indexpage' command? + */ + public IndexSitemapKn(final LogsLocation logsLoc, final Knowledge notIdxSitemap) { + this.logsLoc = logsLoc; + this.notIdxSitemap = notIdxSitemap; + } + + @Override + public Step handle(final Command com) throws IOException { + if("indexsitemap".equalsIgnoreCase(com.type())) { + return new PageHostedOnGithubCheck( + new GeneralPreconditionsCheck( + new SendReply( + new TextReply( + com, + String.format( + com.language().response("index.start.comment"), + com.authorLogin(), this.logsLoc.address() + ) + ), + new IndexSitemap( + new StarRepo( + new SendReply( + new TextReply( + com, + String.format( + com.language().response("index.finished.comment"), + com.authorLogin(), this.logsLoc.address() + ) + ), + new Step.FinalStep() + ) + ) + ) + ) + ), + new SendReply( + new TextReply( + com, + String.format( + com.language().response("denied.badlink.comment"), + com.authorLogin() + ) + ), + new Step.FinalStep() + ) + ); + } + return this.notIdxSitemap.handle(com); + } +} diff --git a/src/test/java/com/amihaiemil/charles/github/HelloTestCase.java b/src/test/java/com/amihaiemil/charles/github/HelloTestCase.java index c4c4ad6..9bc06f0 100644 --- a/src/test/java/com/amihaiemil/charles/github/HelloTestCase.java +++ b/src/test/java/com/amihaiemil/charles/github/HelloTestCase.java @@ -25,14 +25,13 @@ */ package com.amihaiemil.charles.github; +import java.io.IOException; + import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.Test; import org.mockito.Mockito; -import javax.json.Json; -import java.io.IOException; - /** * Unit tests for {@link Hello} * @author Mihai Andronache (amihaiemil@gmail.com) @@ -64,9 +63,9 @@ public Step handle(final Command com) throws IOException { ); Step steps = hello.handle(com); + MatcherAssert.assertThat(steps, Matchers.notNullValue()); MatcherAssert.assertThat( - steps instanceof SendReply, - Matchers.is(true) + steps instanceof SendReply, Matchers.is(true) ); } diff --git a/src/test/java/com/amihaiemil/charles/github/IndexPageKnTestCase.java b/src/test/java/com/amihaiemil/charles/github/IndexPageKnTestCase.java new file mode 100644 index 0000000..cedbb83 --- /dev/null +++ b/src/test/java/com/amihaiemil/charles/github/IndexPageKnTestCase.java @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2016-2017, Mihai Emil Andronache + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1)Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2)Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3)Neither the name of charles-rest nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.amihaiemil.charles.github; + +import java.io.IOException; + +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.Test; +import org.mockito.Mockito; + +/** + * Unit tests for {@link IndexPageKn} + * @author Mihai Andronache (amihaiemil@gmail.com) + * @version $Id$ + * @since 1.0.1 + */ +public final class IndexPageKnTestCase { + + /** + * IndexPageKn can handle an 'indexpage' command. + * @throws Exception If something goes wrong. + */ + @Test + public void handlesIndexPageCommand() throws Exception { + final Command com = Mockito.mock(Command.class); + Mockito.when(com.type()).thenReturn("indexpage"); + Mockito.when(com.authorLogin()).thenReturn("amihaiemil"); + Mockito.when(com.language()).thenReturn(new English()); + + final LogsLocation logs = Mockito.mock(LogsLocation.class); + Mockito.when(logs.address()).thenReturn("/path/to/logs"); + + final Knowledge indexpage = new IndexPageKn( + logs, + new Knowledge() { + @Override + public Step handle(final Command com) throws IOException { + throw new IllegalStateException( + "'indexpage' command was misunderstood!" + ); + } + } + ); + + Step steps = indexpage.handle(com); + MatcherAssert.assertThat(steps, Matchers.notNullValue()); + MatcherAssert.assertThat( + steps instanceof PageHostedOnGithubCheck, Matchers.is(true) + ); + } + + /** + * IndexPageKn can handle a command which is not 'indexsite'. + * @throws Exception If something goes wrong. + */ + @Test + public void handlesNotIndexPageCommand() throws Exception { + final Command com = Mockito.mock(Command.class); + Mockito.when(com.type()).thenReturn("sitemap"); + + final Knowledge indexpage = new IndexPageKn( + Mockito.mock(LogsLocation.class), + new Knowledge() { + @Override + public Step handle(final Command com) throws IOException { + MatcherAssert.assertThat( + com.type(), + Matchers.equalTo("sitemap") + ); + return null; + } + } + ); + indexpage.handle(com); + } +} diff --git a/src/test/java/com/amihaiemil/charles/github/IndexSiteKnTestCase.java b/src/test/java/com/amihaiemil/charles/github/IndexSiteKnTestCase.java new file mode 100644 index 0000000..f44587f --- /dev/null +++ b/src/test/java/com/amihaiemil/charles/github/IndexSiteKnTestCase.java @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2016-2017, Mihai Emil Andronache + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1)Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2)Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3)Neither the name of charles-rest nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.amihaiemil.charles.github; + +import java.io.IOException; + +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.Test; +import org.mockito.Mockito; + +/** + * Unit tests for {@link IndexSiteKn} + * @author Mihai Andronache (amihaiemil@gmail.com) + * @version $Id$ + * @since 1.0.1 + */ +public final class IndexSiteKnTestCase { + + /** + * IndexSiteKn can handle an 'indexsite' command. + * @throws Exception If something goes wrong. + */ + @Test + public void handlesIndexSiteCommand() throws Exception { + final Command com = Mockito.mock(Command.class); + Mockito.when(com.type()).thenReturn("indexsite"); + Mockito.when(com.authorLogin()).thenReturn("amihaiemil"); + Mockito.when(com.language()).thenReturn(new English()); + + final LogsLocation logs = Mockito.mock(LogsLocation.class); + Mockito.when(logs.address()).thenReturn("/path/to/logs"); + + final Knowledge indexsite = new IndexSiteKn( + logs, + new Knowledge() { + @Override + public Step handle(final Command com) throws IOException { + throw new IllegalStateException( + "'indexsite' command was misunderstood!" + ); + } + } + ); + + Step steps = indexsite.handle(com); + MatcherAssert.assertThat(steps, Matchers.notNullValue()); + MatcherAssert.assertThat( + steps instanceof GeneralPreconditionsCheck, Matchers.is(true) + ); + } + + /** + * IndexSiteKn can handle a command which is not 'indexsite'. + * @throws Exception If something goes wrong. + */ + @Test + public void handlesNotIndexSiteCommand() throws Exception { + final Command com = Mockito.mock(Command.class); + Mockito.when(com.type()).thenReturn("hello"); + + final Knowledge indexsite = new IndexSiteKn( + Mockito.mock(LogsLocation.class), + new Knowledge() { + @Override + public Step handle(final Command com) throws IOException { + MatcherAssert.assertThat( + com.type(), + Matchers.equalTo("hello") + ); + return null; + } + } + ); + indexsite.handle(com); + } +} diff --git a/src/test/java/com/amihaiemil/charles/github/IndexSitemapKnTestCase.java b/src/test/java/com/amihaiemil/charles/github/IndexSitemapKnTestCase.java new file mode 100644 index 0000000..9c78f74 --- /dev/null +++ b/src/test/java/com/amihaiemil/charles/github/IndexSitemapKnTestCase.java @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2016-2017, Mihai Emil Andronache + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1)Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2)Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3)Neither the name of charles-rest nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.amihaiemil.charles.github; + +import java.io.IOException; + +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.Test; +import org.mockito.Mockito; + +/** + * Unit tests for {@link IndexSitemapKn} + * @author Mihai Andronache (amihaiemil@gmail.com) + * @version $Id$ + * @since 1.0.1 + */ +public final class IndexSitemapKnTestCase { + + /** + * IndexSitemapKn can handle an 'indexsitemap' command. + * @throws Exception If something goes wrong. + */ + @Test + public void handlesIndexSitemapCommand() throws Exception { + final Command com = Mockito.mock(Command.class); + Mockito.when(com.type()).thenReturn("indexsitemap"); + Mockito.when(com.authorLogin()).thenReturn("amihaiemil"); + Mockito.when(com.language()).thenReturn(new English()); + + final LogsLocation logs = Mockito.mock(LogsLocation.class); + Mockito.when(logs.address()).thenReturn("/path/to/logs"); + + final Knowledge indexsitemap = new IndexSitemapKn( + logs, + new Knowledge() { + @Override + public Step handle(final Command com) throws IOException { + throw new IllegalStateException( + "'indexsitemap' command was misunderstood!" + ); + } + } + ); + + Step steps = indexsitemap.handle(com); + MatcherAssert.assertThat(steps, Matchers.notNullValue()); + MatcherAssert.assertThat( + steps instanceof PageHostedOnGithubCheck, Matchers.is(true) + ); + } + + /** + * IndexSitemapKn can handle a command which is not 'indexsitemap'. + * @throws Exception If something goes wrong. + */ + @Test + public void handlesNotIndexSitemapCommand() throws Exception { + final Command com = Mockito.mock(Command.class); + Mockito.when(com.type()).thenReturn("unknwon"); + + final Knowledge indexsitemap = new IndexSitemapKn( + Mockito.mock(LogsLocation.class), + new Knowledge() { + @Override + public Step handle(final Command com) throws IOException { + MatcherAssert.assertThat( + com.type(), + Matchers.equalTo("unknwon") + ); + return null; + } + } + ); + indexsitemap.handle(com); + } +}