Repository: benjamincorcoran/sasdocs

Issue:
Macro options 
# Issue

Parser fails when macro defined with options i.e. 

```sas
%macro test /des='Hello' secure; 
%mend;
```

Comments:

Patch:
diff --git a/sasdocs/objects.py b/sasdocs/objects.py
index 24ea09f..3958f48 100644
--- a/sasdocs/objects.py
+++ b/sasdocs/objects.py
@@ -52,7 +52,7 @@ def rebuild_macros(objs, i=0):
     while i < len(objs):
         obj = objs[i]
         if len(output) > 0 and type(output[0]) == macroStart and type(obj) == macroEnd:
-            return (macro(name=output[0].name, arguments=output[0].arguments, contents=output[1:]), i)
+            return (macro(name=output[0].name, arguments=output[0].arguments, options=output[0].options, contents=output[1:]), i)
         elif type(obj) != macroStart or (type(obj) == macroStart and len(output)==0) :
             output.append(obj)
         else:
@@ -546,6 +546,7 @@ class macroStart(baseSASObject):
     """
     name = attr.ib()
     arguments = attr.ib()
+    options = attr.ib(default=None)
 
 @attr.s
 class macroEnd(baseSASObject):
@@ -625,6 +626,7 @@ class macro(baseSASObject):
     name = attr.ib()
     arguments = attr.ib()
     contents = attr.ib(repr=False)
+    options = attr.ib(default=None)
 
     def __attrs_post_init__(self):
         self.contents = [obj for obj in self.contents if obj != '\n']
@@ -678,6 +680,7 @@ lb = ps.string('(')
 rb = ps.string(')')
 star = ps.string('*')
 cmm = ps.string(',')
+fs = ps.string('/')
 
 
 # Multiline comment entry and exit points
@@ -721,7 +724,7 @@ mcvDef = ps.seq(
 # e.g. where=(1=1)
 datalineArg = ps.seq(
     option = sasName << (opspc + eq + opspc), 
-    setting = lb + ps.regex(r'[^)]*') + rb
+    setting = lb + ps.regex(r'[^)]*',flags=reFlags) + rb
 ).combine_dict(dataArg)
 
 # datalineArg: Argument in dataline sasName = sasName sasName sasName...
@@ -731,9 +734,13 @@ datalineArgNB = ps.seq(
     setting = ps.regex(r'.*?(?=\s+\w+\s*=)|.*?(?=\))')
 ).combine_dict(dataArg)
 
+datalineArgPt = ps.seq(
+    option = sasName << (opspc + eq + opspc),
+    setting = opspc + qte >> fpth << opspc + qte
+).combine_dict(dataArg)
 
 # datalineOptions: Seperate multiple datalineArgs by spaces
-datalineOptions = lb >> (datalineArg|datalineArgNB|sasName).sep_by(spc) << rb
+datalineOptions = lb >> (datalineArg|datalineArgPt|datalineArgNB|sasName).sep_by(spc) << rb
 
 
 # dataObj: Abstracted data object exists as three components:
@@ -831,7 +838,9 @@ mcroargline = lb + opspc >> mcroarg.sep_by(opspc+cmm+opspc) << opspc + rb
 
 mcroStart = ps.seq(
     name = ps.regex(r'%macro',flags=re.IGNORECASE) + spc + opspc >> sasName,
-    arguments = (opspc >> mcroargline).optional() << opspc + col 
+    arguments = (opspc >> mcroargline).optional(), 
+    options = (opspc + fs + opspc >> (datalineArg|datalineArgPt|sasName).sep_by(spc)).optional(),
+    _col = opspc + col 
 ).combine_dict(macroStart)
 
 mcroEnd = (ps.regex(r'%mend.*?;',flags=re.IGNORECASE)).map(macroEnd)


Test patch:
diff --git a/tests/test_objects.py b/tests/test_objects.py
index c71f223..352ad42 100644
--- a/tests/test_objects.py
+++ b/tests/test_objects.py
@@ -167,6 +167,19 @@ testcases = [
 def test_macroargumentLine_parse(case, expected):
     assert mcroargline.parse(case) == expected
 
+
+testcases = [
+    ('%macro test;', macroStart(name=['test'], arguments=None, options=None)),
+    ('%macro test /des="Description";', macroStart(name=['test'], arguments=None, options=[dataArg(option=['des'],setting='Description')])),
+    ('%macro test /strict des="Description";', macroStart(name=['test'], arguments=None, options=[['strict'], dataArg(option=['des'],setting='Description')]))
+
+]
+@pytest.mark.parametrize("case,expected", testcases)
+def test_macro_start_parse(case, expected):
+    assert mcroStart.parse(case) == expected
+
+
+
 testcases = [
     ('%macro test; %mend;', macro(name=['test'], arguments=None, contents='')),
     ('%macro test(a, b, c); %mend;', macro(name=['test'], arguments=[macroargument(arg=['a'],default=None,doc=None), macroargument(arg=['b'],default=None,doc=None), macroargument(arg=['c'],default=None,doc=None)], contents='')),
@@ -190,6 +203,17 @@ def test_macro_about_parse(case, expected):
     macro = force_partial_parse(fullprogram,case)[0]
     assert macro.about == expected
 
+testcases = [
+    ('%macro test; /*This is the test macro*/ %mend;', None),
+    ('%macro test /strict; /*This is the test macro*/\n/*This is the second line*/%mend;', [['strict']]),
+    ('%macro test /strict des="Description"; data a; set b; run; /*This is the test macro*/ %mend;', [['strict'], dataArg(option=['des'],setting='Description')]),
+]
+
+@pytest.mark.parametrize("case,expected", testcases)
+def test_macro_options_parse(case, expected):
+    macro = force_partial_parse(fullprogram,case)[0]
+    assert macro.options == expected
+
 testcases = [
     ('%macro test; data a; set b; run; %mend;', [dataStep(outputs=[dataObject(library=None, dataset=['a'], options=None)], header=' ', inputs=[dataObject(library=None, dataset=['b'], options=None)], body=' ')]),
     ('%macro test(a=1/*Doc A*/,b/*Doc B*/); data a; set b; run; %mend;', [dataStep(outputs=[dataObject(library=None, dataset=['a'], options=None)], header=' ', inputs=[dataObject(library='work', dataset=['b'])], body=' ')]),