From 4484ee053a5651c3c6cb3fb0520cf8aba991ec66 Mon Sep 17 00:00:00 2001 From: Mandar Chitre Date: Fri, 29 Nov 2024 13:33:40 +0800 Subject: [PATCH] feat(shell): dynamic help from agents using __doc__ parameter --- VERSION | 2 +- .../arl/fjage/shell/BaseGroovyScript.groovy | 6 +- .../org/arl/fjage/shell/Documentation.java | 116 +++++++++++++----- 3 files changed, 90 insertions(+), 34 deletions(-) diff --git a/VERSION b/VERSION index 850e7424..141f2e80 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.14.0 +1.15.0 diff --git a/src/main/groovy/org/arl/fjage/shell/BaseGroovyScript.groovy b/src/main/groovy/org/arl/fjage/shell/BaseGroovyScript.groovy index 604fb15c..ef6097bc 100644 --- a/src/main/groovy/org/arl/fjage/shell/BaseGroovyScript.groovy +++ b/src/main/groovy/org/arl/fjage/shell/BaseGroovyScript.groovy @@ -495,7 +495,8 @@ abstract class BaseGroovyScript extends Script { Binding binding = getBinding() if (binding.hasVariable('__doc__')) { def doc = binding.getVariable('__doc__') - return doc.get(obj as String) + Agent a = binding.hasVariable('__agent__') ? binding.getVariable('__agent__') : null + return doc.get(a, obj as String) } return null } @@ -509,7 +510,8 @@ abstract class BaseGroovyScript extends Script { Binding binding = getBinding() if (binding.hasVariable('__doc__')) { def doc = binding.getVariable('__doc__') - return doc.get() + Agent a = binding.hasVariable('__agent__') ? binding.getVariable('__agent__') : null + return doc.get(a) } return null } diff --git a/src/main/java/org/arl/fjage/shell/Documentation.java b/src/main/java/org/arl/fjage/shell/Documentation.java index 2024c03e..89b29368 100644 --- a/src/main/java/org/arl/fjage/shell/Documentation.java +++ b/src/main/java/org/arl/fjage/shell/Documentation.java @@ -13,13 +13,20 @@ import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.arl.fjage.*; +import org.arl.fjage.param.*; public class Documentation { protected List doc = new ArrayList(); - protected Map ndx = new HashMap(); - protected Pattern heading = Pattern.compile("^#+ +([^ ]+) +-.*$"); - protected Pattern section = Pattern.compile("^#+ .*$"); + protected int staticSize = 0; + + protected static final Pattern heading = Pattern.compile("^#+ +([^ ]+) +-.*$"); + protected static final Pattern section = Pattern.compile("^#+ .*$"); + protected static final String crlf = "\\r?\\n"; + protected static final String header = "^#+ +"; + protected static final String gaps = "\\n+$"; + protected static final String placeholder = "@@"; // to be replaced by agent name /** * Add markdown documentation. @@ -27,67 +34,87 @@ public class Documentation { * @param s multiline string. */ public void add(String s) { - String[] lines = s.split("\\r?\\n"); - for (String line: lines) { - Matcher m = heading.matcher(line); - if (m.matches()) ndx.put(m.group(1), doc.size()); + if (doc.size() > staticSize) doc.subList(staticSize, doc.size()).clear(); + String[] lines = s.split(crlf); + for (String line: lines) doc.add(line); - } + staticSize = doc.size(); } /** * Get documentation index. */ - public String get() { + public String get(Agent agent) { + build(agent); + List topics = new ArrayList(); + for (String s: doc) + if (s.startsWith("# ")) topics.add(s.substring(2)); + topics.sort(null); StringBuilder sb = new StringBuilder(); - for (Integer v: ndx.values()) { - String s = doc.get(v); - if (s.startsWith("# ")) { - sb.append(s.substring(2)); - sb.append('\n'); - } + for (String s: topics) { + sb.append(s); + sb.append('\n'); } return sb.toString(); } + /** + * Get documentation by keyword. + * + * @param keyword keyword. + */ + public String get(Agent agent, String keyword) { + build(agent); + int count = 0; + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < doc.size(); i++) { + String s = doc.get(i); + Matcher m = heading.matcher(s); + if (m.matches() && m.group(1).equals(keyword)) { + extract(sb, i); + count++; + } + } + if (count == 0) search(sb, keyword); + return sb.toString().replaceAll(gaps, "\n"); + } + /** * Search documentation topics by keyword. * + * @param sb string builder. * @param keyword keyword. */ - public String search(String keyword) { + protected void search(StringBuilder sb, String keyword) { keyword = keyword.toLowerCase(); Set topics = new HashSet(); String topic = null; for (String s: doc) { Matcher m = section.matcher(s); - if (m.matches()) topic = s.replaceAll("^#+ +", "- "); + if (m.matches()) topic = s.replaceAll(header, "- "); if (s.toLowerCase().contains(keyword)) topics.add(topic); } - if (topics.size() == 0) return null; - StringBuilder sb = new StringBuilder(); + if (topics.size() == 0) return; sb.append("Possible topics:\n"); for (String s: topics) { sb.append(s); sb.append('\n'); } - return sb.toString(); } /** - * Get documentation by keyword. + * Extract documentation. * - * @param keyword keyword. + * @param sb string builder. + * @param pos position. */ - public String get(String keyword) { - Integer pos = ndx.get(keyword); - if (pos == null) return search(keyword); + protected void extract(StringBuilder sb, int pos) { String s = doc.get(pos); int level = s.indexOf(' '); - if (level <= 0) return null; + if (level <= 0) return; int endlevel = level; - StringBuilder sb = new StringBuilder(); - sb.append(s.replaceAll("^#+ +", "")); + if (s.startsWith("# ")) sb.append(s); + else sb.append(s.replaceAll(header, "")); sb.append('\n'); int skip = 0; for (int i = pos+1; i < doc.size(); i++) { @@ -101,11 +128,11 @@ public String get(String keyword) { if (skip == 0 || level <= skip) { if (skip > 0) nl = true; skip = 0; - s = s.replaceAll("^#+ +", ""); + s = s.replaceAll(header, ""); } if (m.matches()) { skip = level; - sb.append("- "); + sb.append("* "); sb.append(s); sb.append('\n'); } else if (nl) { @@ -117,7 +144,34 @@ public String get(String keyword) { sb.append('\n'); } } - return sb.toString().replaceAll("\\n+$", "\n"); + sb.append('\n'); + } + + /** + * Build dynamic documentation by querying agents. + * + * @param agent agent. + */ + protected void build(Agent agent) { + if (agent == null) return; + if (doc.size() > staticSize) doc.subList(staticSize, doc.size()).clear(); + Container c = agent.getContainer(); + AgentID[] agentIDs = c.getAgents(); + for (AgentID a: agentIDs) { + ParameterReq req = new ParameterReq(); + req.setRecipient(a); + req.get(new NamedParameter("__doc__")); + Message rsp = agent.request(req, 1000); + if (rsp != null && rsp instanceof ParameterRsp) { + Object docstr = ((ParameterRsp)rsp).get(new NamedParameter("__doc__")); + if (docstr != null) { + String s = docstr.toString().replaceAll(placeholder, a.getName()); + String[] lines = s.split(crlf); + for (String line: lines) + doc.add(line); + } + } + } } }