import bionetgen as bng
import os, re
from bionetgen.main import BioNetGen
from bionetgen.core.exc import BNGFileError
from bionetgen.core.utils.utils import find_BNG_path, run_command, ActionList
from tempfile import TemporaryDirectory
# This allows access to the CLIs config setup
app = BioNetGen()
app.setup()
conf = app.config["bionetgen"]
def_bng_path = conf["bngpath"]
[docs]class BNGFile:
"""
File object designed to deal with .bngl file manipulations.
Usage: BNGFile(bngl_path)
BNGFile(bngl_path, BNGPATH)
Attributes
----------
path : str
path to the file the object needs to deal with
_action_list : list[str]
list of acceptible actions
BNGPATH : str
optional path to bng folder that contains BNG2.pl
bngexec : str
path to BNG2.pl
Methods
-------
generate_xml(xml_file, model_file=None) : bool
takes the given BNGL file and generates a BNG-XML from it
strip_actions(model_path, folder) : str
deletes actions from a given BNGL file
write_xml(open_file, xml_type="bngxml", bngl_str=None) : bool
given a bngl file or a string, writes an SBML or BNG-XML from it
"""
def __init__(self, path, BNGPATH=def_bng_path, generate_network=False) -> None:
self.path = path
self.generate_network = generate_network
AList = ActionList()
self._action_list = [i + "(" for i in AList.possible_types]
BNGPATH, bngexec = find_BNG_path(BNGPATH)
self.BNGPATH = BNGPATH
self.bngexec = bngexec
self.parsed_actions = []
[docs] def generate_xml(self, xml_file, model_file=None) -> bool:
"""
generates an BNG-XML file from a given model file. Defaults
to self.path if model_file is not given
"""
if model_file is None:
model_file = self.path
cur_dir = os.getcwd()
# temporary folder to work in
with TemporaryDirectory() as temp_folder:
# make a stripped copy without actions in the folder
stripped_bngl = self.strip_actions(model_file, temp_folder)
# run with --xml
os.chdir(temp_folder)
# TODO: take stdout option from app instead
rc, _ = run_command(["perl", self.bngexec, "--xml", stripped_bngl])
if rc == 1:
# if we fail, print out what we have to
# let the user know what BNG2.pl says
# if rc.stdout is not None:
# print(rc.stdout.decode('utf-8'))
# if rc.stderr is not None:
# print(rc.stderr.decode('utf-8'))
# go back to our original location
os.chdir(cur_dir)
# shutil.rmtree(temp_folder)
return False
else:
# we should now have the XML file
path, model_name = os.path.split(stripped_bngl)
model_name = model_name.replace(".bngl", "")
written_xml_file = model_name + ".xml"
with open(written_xml_file, "r", encoding="UTF-8") as f:
content = f.read()
xml_file.write(content)
# since this is an open file, to read it later
# we need to go back to the beginning
xml_file.seek(0)
# go back to our original location
os.chdir(cur_dir)
return True
[docs] def strip_actions(self, model_path, folder) -> str:
"""
Strips actions from a BNGL file and makes a copy
into the given folder
"""
# Get model name and setup path stuff
path, model_file = os.path.split(model_path)
# open model and strip actions
with open(model_path, "r", encoding="UTF-8") as mf:
# read and strip actions
mstr = mf.read()
# TODO: Clean this up _a lot_
# this removes any new line escapes (\ \n) to continue
# to another line, so we can just remove the action lines
mstr = re.sub(r"\\\n", "", mstr)
mlines = mstr.split("\n")
stripped_lines = list(filter(lambda x: self._not_action(x), mlines))
# remove spaces, actions don't allow them
self.parsed_actions = [
x.replace(" ", "")
for x in filter(lambda x: not self._not_action(x), mlines)
]
# let's remove begin/end actions, rarely used but should be removed
remove_from = -1
remove_to = -1
for iline, line in enumerate(stripped_lines):
if re.match(r"\s*(begin)\s+(actions)\s*", line):
remove_from = iline
elif re.match(r"\s*(end)\s+(actions)\s*", line):
remove_to = iline
if remove_from > 0:
# we have a begin/end actions block
if remove_to < 0:
msg = f'There is a "begin actions" statement at line {remove_from} without a matching "end actions" statement'
raise BNGFileError(model_path, message=msg)
stripped_lines = (
stripped_lines[:remove_from] + stripped_lines[remove_to + 1 :]
)
if remove_to > 0:
if remove_from < 0:
msg = f'There is an "end actions" statement at line {remove_to} without a matching "begin actions" statement'
raise BNGFileError(model_path, message=msg)
# TODO: read stripped lines and store the actions
# open new file and write just the model
stripped_model = os.path.join(folder, model_file)
if self.generate_network:
stripped_lines += ["generate_network({overwrite=>1})"]
stripped_lines = [x + "\n" for x in stripped_lines]
with open(stripped_model, "w", encoding="UTF-8") as sf:
sf.writelines(stripped_lines)
return stripped_model
def _not_action(self, line) -> bool:
for action in self._action_list:
if action in line:
return False
return True
[docs] def write_xml(self, open_file, xml_type="bngxml", bngl_str=None) -> bool:
"""
write new BNG-XML or SBML of file by calling BNG2.pl again
or can take BNGL string in as well.
"""
# TODO: Implement the route where this function uses the file itself
# for this generation
if bngl_str is None:
# should load in the right str here
raise NotImplementedError
cur_dir = os.getcwd()
# temporary folder to work in
with TemporaryDirectory() as temp_folder:
# write the current model to temp folder
os.chdir(temp_folder)
with open("temp.bngl", "w", encoding="UTF-8") as f:
f.write(bngl_str)
# run with --xml
# TODO: Make output supression an option somewhere
if xml_type == "bngxml":
rc, _ = run_command(["perl", self.bngexec, "--xml", "temp.bngl"])
if rc == 1:
print("XML generation failed")
# go back to our original location
os.chdir(cur_dir)
return False
else:
# we should now have the XML file
with open("temp.xml", "r", encoding="UTF-8") as f:
content = f.read()
open_file.write(content)
# go back to beginning
open_file.seek(0)
os.chdir(cur_dir)
return True
elif xml_type == "sbml":
command = ["perl", self.bngexec, "temp.bngl"]
rc, _ = run_command(command)
if rc == 1:
print("SBML generation failed")
# go back to our original location
os.chdir(cur_dir)
return False
else:
# we should now have the SBML file
with open("temp_sbml.xml", "r", encoding="UTF-8") as f:
content = f.read()
open_file.write(content)
open_file.seek(0)
os.chdir(cur_dir)
return True
else:
print("XML type {} not recognized".format(xml_type))
return False