In Blue Prism, the parts that actually operate the application called Object and the parts that write the business logic called Process are separated, and it is a mechanism to improve reusability. In order to promote the reuse of Objects within the team, it is necessary to create and maintain a document "What kind of Object is there", but it is difficult to create and maintain by hand, and the contents of the Object have been updated. There may be problems such as not being able to follow.
This article deals with the story of automatically generating documentation for such important Objects (ODI: Object Design Instruction in Blue Prism terminology).
I have a VBO called "Object Inventory" in Ditigal Exchange (https://digitalexchange.blueprism.com/dx/entry/3439/solution/object-inventory), but the string trim is supposed to be in English. Or it didn't work because I got a mysterious Excel error (Exception: Exception from HRESULT: 0x800A03EC
). .. ..
However, there was the following description on the help page.
This asset is a business object that uses the output from the BP command line /getbod function to create a list of all business objects, pages, descriptions, inputs and outputs.
The fact that AutomateC.exe has a switch called ``` / getbod``` means that the Help Command Line Options (https://bpdocs.blueprism.com/bp-6-8/ja-jp/helpCommandLine. It is not written in htm) either. .. ..
# Switches called / listprocesses and / getbod
Looking into the "Object Inventory" VBO above, it seems that the ``` / listprocesses``` switch gets a list of Processes and Objects and calls` `/ getbod``` for each. If the target is Process, the string `` `Could not find Business Object` `` will be returned, so it seems that they are excluded from the processing target.
```/getbod```The switch really existed!
# Try to implement with python
It's a poor script, but it works. We hope for your reference. (Verified with Python 3.7.4)
## For listprocesses, getbod ones are saved in a text file.
```python
"""
BP parameters for connecting to Blue Prism_USERNAME, BP_PASSWORD, BP_Set it in the DBCONNAME environment variable and execute.
BP_USERNAME :username
BP_PASSWORD :password
BP_DBCONNAME :Connection name
"""
import delegator
from pathlib import Path
import logging
import os
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
bp_username = os.environ["BP_USERNAME"]
bp_password = os.environ["BP_PASSWORD"]
bp_dbconname = os.environ["BP_DBCONNAME"]
LISTPROCESSES_CMD = '"C:\Program Files\Blue Prism Limited\Blue Prism Automate\AutomateC.exe" /user {bp_username} {bp_password} /dbconname {bp_dbconname} /listprocesses'
command = LISTPROCESSES_CMD.format(
bp_username=bp_username, bp_password=bp_password, bp_dbconname=bp_dbconname
)
context = delegator.run(command)
object_list = context.out
object_names = object_list.splitlines()
logger.info(object_names)
GETBOD_CMD = '"C:\Program Files\Blue Prism Limited\Blue Prism Automate\AutomateC.exe" /user {bp_username} {bp_password} /dbconname {bp_dbconname} /getbod "{object_name}"'
for object_name in object_names:
command = GETBOD_CMD.format(
bp_username=bp_username,
bp_password=bp_password,
bp_dbconname=bp_dbconname,
object_name=object_name,
)
context = delegator.run(command)
description = context.out
if (
len(description.splitlines()) <= 1
): #Process is explained"The business object XX was not found"Only one line is output
logger.info("{} is not a object".format(object_name))
continue
#If slash is included in the file name, the file cannot be created, so replace it.
description_file_name = object_name.replace("/", "_") + ".txt"
with open(Path("output_descriptions") / description_file_name, "w") as f:
f.write(context.out)
Please let me know if there is a library that can be parsed more easily. .. ..
from typing import List, Optional
from dataclasses import dataclass, field
import re
import enum
import logging
from pathlib import Path
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG)
@dataclass
class VBOActionDescription:
"""
A class that holds information about each action in the VBO
"""
action_name: str
description: str = ""
pre_condition: str = ""
post_condition: str = ""
input_params: List[str] = field(default_factory=list)
output_params: List[str] = field(default_factory=list)
def as_markdown(self) -> str:
"""
Express in Markdown format
"""
_md = (
"###{action_name}\n"
"{description}\n"
"\n"
"####Pre-conditions\n"
"{pre_condition}\n"
"\n"
"####Post-conditions\n"
"{post_condition}\n"
"\n"
"####Input parameters\n"
"{input_params}\n"
"\n"
"####Output parameters\n"
"{output_params}\n"
"\n"
)
input_params_md = ("\n").join(
["* {}".format(input_param) for input_param in self.input_params]
)
output_params_md = ("\n").join(
["* {}".format(output_param) for output_param in self.output_params]
)
out = _md.format(
action_name=self.action_name,
description=self.description,
pre_condition=self.pre_condition,
post_condition=self.post_condition,
input_params=input_params_md,
output_params=output_params_md,
)
return out
class VBODescription:
"""
A class that holds VBO information. Have a small list of VBOActionDescription
"""
def __init__(
self,
object_name: str,
description: Optional[str] = "",
mode: str = "",
actions: Optional[List[VBOActionDescription]] = None,
):
self.object_name = object_name
self.description = description
self.mode = mode
self.actions = actions
def as_markdown(self) -> str:
"""
Express in Markdown format
"""
_md = (
"#{object_name}\n"
"{description}\n"
"\n"
"##action mode\n"
"{mode}\n"
"\n"
"##action\n"
)
out = _md.format(
object_name=self.object_name, description=self.description, mode=self.mode
)
if self.actions:
out = out + ("\n").join([action.as_markdown() for action in self.actions])
return out
class DescriptionOfWhat(enum.Enum):
"""
When parsing the description of VBO, I needed the information "Which part are you reading?"
"""
business_object = "Business Object"
action = "Action"
pre_condition = "Pre Conditiion"
post_condition = "Post Conditiion"
def classify_line(line: str):
"""
Classify lines
"""
line = line.strip()
# =Business object- Utility - File Management= <=Match the line like the one on the left
match = re.search("^=(?P<content>[^=]*)=$", line)
if match:
return {"type": "object name", "content": match.groupdict()["content"]}
#The execution mode of this business object is "background"<=Match the line like the one on the left
match = re.search("^The execution mode of this business object is "(?P<mode>[^」]+)"is", line)
if match:
return {"type": "mode", "content": match.groupdict()["mode"]}
# ==Append to Text File== <=Match the line like the one on the left
match = re.search("^==(?P<content>[^=]*)==$", line)
if match:
return {"type": "action name", "content": match.groupdict()["content"]}
# ===Prerequisites=== <=Match the line like the one on the left
match = re.search("^===(?P<content>[^=]*)===$", line)
if match:
content = match.groupdict()["content"]
if content == "Prerequisites": #The translation on the Blue Prism side is strange. .. ..
return {"type": "pre condition", "content": content}
if content == "end point": #The translation on the Blue Prism side is strange. .. ..
return {"type": "post condition", "content": content}
#other than that
return {"type": "action attribute", "content": content}
# *input:File Path (text) - Full path to the file to get the file size <=Match the line like the one on the left
match = re.search("^\*input:(?P<content>.*)$", line)
if match:
return {"type": "input parameter", "content": match.groupdict()["content"]}
# *output:File Path (text) - Full path to the file to get the file size <=Match the line like the one on the left
match = re.search("^\*output:(?P<content>.*)$", line)
if match:
return {"type": "output parameter", "content": match.groupdict()["content"]}
#Other lines
return {"type": "article", "content": line}
def append_action_to_vbo_description(latest_action, vbo_description):
actions = vbo_description.actions
if actions:
vbo_description.actions.append(latest_action)
else:
vbo_description.actions = [
latest_action,
]
return vbo_description
def convert_to_markdown(bod_description_filepath) -> str:
"""
The body of the process to convert to Markdown
"""
vbo_description = None
with open(bod_description_filepath, "r", encoding="shift_jis", newline="\r\n") as f:
previous_line_type: Optional[DescriptionOfWhat] = None #Keep track of what you are reading.
latest_action = None
for line in f:
line_class = classify_line(line)
if line_class["type"] == "object name":
vbo_description = VBODescription(line_class["content"])
previous_line_type = DescriptionOfWhat.business_object
continue
if line_class["type"] == "mode":
assert vbo_description, "Execution mode is not described in the correct position"
vbo_description.mode = line_class["content"]
continue
if line_class["type"] == "article":
assert vbo_description, "Not written in the correct format"
if previous_line_type == DescriptionOfWhat.business_object:
vbo_description.description += line_class["content"]
continue
if previous_line_type == DescriptionOfWhat.action:
assert latest_action, "Not written in the correct format"
latest_action.description += line_class["content"]
continue
if previous_line_type == DescriptionOfWhat.pre_condition:
assert latest_action, "Not written in the correct format"
latest_action.pre_condition += line_class["content"]
continue
if previous_line_type == DescriptionOfWhat.post_condition:
assert latest_action, "Not written in the correct format"
latest_action.post_condition += line_class["content"]
continue
if line_class["type"] == "action name":
assert vbo_description, "Not written in the correct format"
if latest_action:
vbo_description = append_action_to_vbo_description(
latest_action, vbo_description
)
latest_action = VBOActionDescription(line_class["content"])
previous_line_type = DescriptionOfWhat.action
continue
if line_class["type"] == "input parameter":
assert vbo_description and latest_action, "Not written in the correct format"
latest_action.input_params.append(line_class["content"])
continue
if line_class["type"] == "output parameter":
assert vbo_description and latest_action, "Not written in the correct format"
latest_action.output_params.append(line_class["content"])
continue
if line_class["type"] == "pre condition":
assert vbo_description and latest_action, "Not written in the correct format"
previous_line_type = DescriptionOfWhat.pre_condition
continue
if line_class["type"] == "post condition":
assert vbo_description and latest_action, "Not written in the correct format"
previous_line_type = DescriptionOfWhat.post_condition
continue
# debug
logger.debug("line: {}".format(line.strip()))
if latest_action:
logger.debug("latest_action: {}".format(latest_action.as_markdown()))
else:
#Last remaining latest_Collect action
if latest_action:
vbo_description = append_action_to_vbo_description(
latest_action, vbo_description
)
assert vbo_description, "Not written in the correct format"
return vbo_description.as_markdown()
if __name__ == "__main__":
descriptions_folder = Path("output_descriptions")
for description_file in descriptions_folder.glob("*.txt"):
with open(descriptions_folder / (description_file.stem + ".md"), "w") as md_f:
md_f.write(convert_to_markdown(description_file))
Recommended Posts