Commit 6c794d2e authored by Pierre-Antoine Rouby's avatar Pierre-Antoine Rouby
Browse files

Solver: Refactoring command line parser and add unittest.

Showing with 232 additions and 23 deletions
+232 -23
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
import os import os
import logging import logging
from tools import timer from tools import timer, parse_command_line
try: try:
# Installation allow Unix-like signal # Installation allow Unix-like signal
...@@ -146,9 +146,8 @@ class CommandLineSolver(AbstractSolver): ...@@ -146,9 +146,8 @@ class CommandLineSolver(AbstractSolver):
Returns: Returns:
The executable and list of arguments The executable and list of arguments
""" """
# HACK: Works in most case... Trust me i'm an engineer
cmd = cmd.replace("@install_dir", self._install_dir()) cmd = cmd.replace("@install_dir", self._install_dir())
cmd = cmd.replace("@path", path.replace(" ", "%20")) cmd = cmd.replace("\"@path\"", path)
cmd = cmd.replace("@input", self.input_param()) cmd = cmd.replace("@input", self.input_param())
cmd = cmd.replace("@output", self.output_param()) cmd = cmd.replace("@output", self.output_param())
cmd = cmd.replace("@dir", self._process.workingDirectory()) cmd = cmd.replace("@dir", self._process.workingDirectory())
...@@ -156,26 +155,9 @@ class CommandLineSolver(AbstractSolver): ...@@ -156,26 +155,9 @@ class CommandLineSolver(AbstractSolver):
logger.debug(f"! {cmd}") logger.debug(f"! {cmd}")
if cmd[0] == "\"": words = parse_command_line(cmd)
# Command line executable path is between " char exe = words[0]
cmd = cmd.split("\"") args = words[1:]
exe = cmd[1].replace("%20", " ")
args = list(
filter(
lambda s: s != "",
"\"".join(cmd[2:]).split(" ")[1:]
)
)
else:
# We suppose the command line executable path as no space char
cmd = cmd.replace("\\ ", "&_&").split(" ")
exe = cmd[0].replace("&_&", " ")
args = list(
filter(
lambda s: s != "",
map(lambda s: s.replace("&_&", " "), cmd[1:])
)
)
logger.info(f"! {exe} {args}") logger.info(f"! {exe} {args}")
return exe, args return exe, args
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
from Solver.CommandLine import CommandLineSolver from Solver.CommandLine import CommandLineSolver
class GenericSolver(CommandLineSolver): class GenericSolver(CommandLineSolver):
_type = "generic" _type = "generic"
......
# __init__.py -- Pamhyr
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
# test_pamhyr.py -- Pamhyr
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import os
import unittest
import tempfile
from tools import parse_command_line
class ToolsCMDParserTestCase(unittest.TestCase):
def test_trivial(self):
cmd = "foo"
expect = ["foo"]
res = parse_command_line(cmd)
for i, s in enumerate(expect):
self.assertEqual(res[i], s)
def test_unix_simple(self):
cmd = "/foo/bar a -b -c"
expect = ["/foo/bar", "a", '-b', "-c"]
res = parse_command_line(cmd)
for i, s in enumerate(expect):
self.assertEqual(res[i], s)
def test_unix_quoted(self):
cmd = "\"/foo/bar\" -a -b -c"
expect = ["/foo/bar", "-a", '-b', "-c"]
res = parse_command_line(cmd)
for i, s in enumerate(expect):
self.assertEqual(res[i], s)
def test_unix_quoted_with_space(self):
cmd = "\"/foo/bar baz\" -a -b -c"
expect = ["/foo/bar baz", "-a", '-b', "-c"]
res = parse_command_line(cmd)
for i, s in enumerate(expect):
self.assertEqual(res[i], s)
def test_unix_quoted_args(self):
cmd = "/foo/bar -a -b -c=\"baz\""
expect = ["/foo/bar", "-a", '-b', "-c=\"baz\""]
res = parse_command_line(cmd)
for i, s in enumerate(expect):
self.assertEqual(res[i], s)
def test_unix_quoted_args_with_space(self):
cmd = "/foo/bar -a -b -c=\"baz bazz\""
expect = ["/foo/bar", "-a", '-b', "-c=\"baz bazz\""]
res = parse_command_line(cmd)
for i, s in enumerate(expect):
self.assertEqual(res[i], s)
def test_windows_prog_files(self):
cmd = "\"C:\\Program Files (x86)\foo\bar\" a -b -c"
expect = ["C:\\Program Files (x86)\foo\bar", "a", '-b', "-c"]
res = parse_command_line(cmd)
for i, s in enumerate(expect):
self.assertEqual(res[i], s)
def test_windows_prog_files_args(self):
cmd = "\"C:\\Program Files (x86)\foo\bar\" a -b=\"baz bazz\" -c"
expect = [
"C:\\Program Files (x86)\foo\bar",
"a", '-b=\"baz bazz\"', "-c"
]
res = parse_command_line(cmd)
for i, s in enumerate(expect):
self.assertEqual(res[i], s)
...@@ -341,3 +341,112 @@ class SQL(object): ...@@ -341,3 +341,112 @@ class SQL(object):
def _load(self): def _load(self):
logger.warning("TODO: LOAD") logger.warning("TODO: LOAD")
#######################
# COMMAND LINE PARSER #
#######################
parser_special_char = ["\"", "\'"]
def parse_command_line(cmd):
"""Parse command line string and return list of string arguments
Parse command line string and returns the list of separate
arguments as string, this function take in consideration space
separator and quoted expression
Args:
cmd: The command line to parce
Returns:
List of arguments as string
"""
words = []
rest = cmd
while True:
if len(rest) == 0:
break
word, rest = _parse_next_word(rest)
words.append(word)
return words
def _parse_next_word(words):
"""Parse the next word in words string
Args:
words: The words string
Returns:
the next word and rests of words
"""
if len(words) == 1:
return words, ""
# Remove useless space
words = words.strip()
# Parse
if words[0] == "\"":
word, rest = _parse_word_up_to_next_sep(words, sep="\"")
elif words[0] == "\'":
word, rest = _parse_word_up_to_next_sep(words, sep="\'")
else:
word, rest = _parse_word_up_to_next_sep(words, sep=" ")
return word, rest
def _parse_word_up_to_next_sep(words, sep=" "):
word = ""
i = 0 if sep == " " else 1
cur = words[i]
skip_next = False
while True:
# Exit conditions
if cur == "":
break
if cur == sep:
if not skip_next:
break
# Take in consideration escape char in case of \<sep>
if cur == "\\":
# If previous char is a escape char, cancel next char
# skiping:
# \<sep> -> skip <sep> as separator
# \\<sep> -> do not skip <sep>
skip_next = not skip_next
else:
skip_next = False
word += cur
# Current word contain a word with different separator,
# typicaly, the string '-c="foo bar"' with ' ' seperator must
# be parse as one word.
#
# Correct: '-c="foo bar" baz' -> '-c="foo bar"', 'baz'
# Not correct: '-c="foo bar" baz' -> '-c="foo', 'bar" baz'
if cur in parser_special_char:
# Recursive call to parse this word
sub_word, rest = _parse_word_up_to_next_sep(words[i:], sep=cur)
i += len(sub_word) + 1
word += sub_word + cur
# Get next symbol
i += 1
if i == len(words):
cur = ""
else:
cur = words[i]
rest = words[i+1:]
return word, rest
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment