initial version
4 files changed, 331 insertions(+), 0 deletions(-)

A => pyjed/__init__.py
A => pyjed/jcl.py
A => pyproject.toml
A => tests/test_pyjed.py
A => pyjed/__init__.py +7 -0
@@ 0,0 1,7 @@ 
+from pyjed.jcl import (DD,
+                       EXEC,
+                       JOB,
+                       )
+
+__version__ = '0.1.0'
+__all__ = ('DD', 'EXEC', 'JOB')

          
A => pyjed/jcl.py +210 -0
@@ 0,0 1,210 @@ 
+from collections import UserList
+from dataclasses import dataclass, field, fields, make_dataclass
+
+
+@dataclass
+class Base:
+    def to_string(self, statement):
+        for parm in fields(self):
+            if (parm.name != 'name' and
+               getattr(self, parm.name) is not None and
+               parm.name not in self._attrs):
+                name, value = parm.name.upper().replace('_', ''), getattr(self, parm.name)
+                if isinstance(value, str) and (
+                   ' ' in value or [c for c in value if c.islower()]):
+                    value = f"'{value}'"
+                if len(statement[-1]) + len(f'{name}={value},') > 78:
+                    statement.append('//      ')
+                statement[-1] = statement[-1] + f'{name}={value},'
+        print(statement)
+        statement[-1] = statement[-1][:-1]
+        return statement
+
+
+@dataclass
+class DISP:
+    status: str = ''
+    normal: str = ''
+    abnormal: str = ''
+
+
+@dataclass
+class DD(Base):
+    name: str
+    stream: str = field(default=None, repr=False)
+    accode: str = field(default=None, repr=False)
+    amp: str = field(default=None, repr=False)
+    avgrec: str = field(default=None, repr=False)
+    blksize: str = field(default=None, repr=False)
+    blkszlim: str = field(default=None, repr=False)
+    burst: str = field(default=None, repr=False)
+    ccsid: str = field(default=None, repr=False)
+    chars: str = field(default=None, repr=False)
+    chkpt: str = field(default=None, repr=False)
+    cntl: str = field(default=None, repr=False)
+    copies: str = field(default=None, repr=False)
+    data: str = field(default=None, repr=False)
+    dataclas: str = field(default=None, repr=False)
+    dcb: str = field(default=None, repr=False)
+    ddname: str = field(default=None, repr=False)
+    dest: str = field(default=None, repr=False)
+    disp: str = field(default=None)
+    dlm: str = field(default=None, repr=False)
+    dsid: str = field(default=None, repr=False)
+    dsname: str = field(default=None)
+    dsntype: str = field(default=None, repr=False)
+    dummy: str = field(default=None, repr=False)
+    dynam: str = field(default=None, repr=False)
+    eattr: str = field(default=None, repr=False)
+    expdt: str = field(default=None, repr=False)
+    fcb: str = field(default=None, repr=False)
+    filedata: str = field(default=None, repr=False)
+    flash: str = field(default=None, repr=False)
+    free: str = field(default=None, repr=False)
+    freevol: str = field(default=None, repr=False)
+    gdgorder: str = field(default=None, repr=False)
+    hold: str = field(default=None, repr=False)
+    keylabl1: str = field(default=None, repr=False)
+    keylabl2: str = field(default=None, repr=False)
+    keyencd1: str = field(default=None, repr=False)
+    keyencd2: str = field(default=None, repr=False)
+    keylen: str = field(default=None, repr=False)
+    keyoff: str = field(default=None, repr=False)
+    label: str = field(default=None, repr=False)
+    lgstream: str = field(default=None, repr=False)
+    like: str = field(default=None, repr=False)
+    lrecl: str = field(default=None, repr=False)
+    maxgens: str = field(default=None, repr=False)
+    mgmtclas: str = field(default=None, repr=False)
+    modify: str = field(default=None, repr=False)
+    outlim: str = field(default=None, repr=False)
+    output: str = field(default=None, repr=False)
+    path: str = field(default=None)
+    pathdisp: str = field(default=None, repr=False)
+    pathmode: str = field(default=None, repr=False)
+    pathopts: str = field(default=None, repr=False)
+    protect: str = field(default=None, repr=False)
+    recfm: str = field(default=None, repr=False)
+    recorg: str = field(default=None, repr=False)
+    refdd: str = field(default=None, repr=False)
+    retpd: str = field(default=None, repr=False)
+    rls: str = field(default=None, repr=False)
+    secmodel: str = field(default=None, repr=False)
+    segment: str = field(default=None, repr=False)
+    space: str = field(default=None, repr=False)
+    spin: str = field(default=None, repr=False)
+    storclas: str = field(default=None, repr=False)
+    subsys: str = field(default=None, repr=False)
+    symbols: str = field(default=None, repr=False)
+    symlist: str = field(default=None, repr=False)
+    sysout: str = field(default=None)
+    term: str = field(default=None, repr=False)
+    ucs: str = field(default=None, repr=False)
+    unit: str = field(default=None, repr=False)
+    volume: str = field(default=None)
+
+    _attrs = ('stream')
+    _alias = {'VOLUME': 'VOL'}
+
+    def to_string(self):
+        if self.stream:
+            stream = super().to_string([f'//{self.name} DD * '])
+            stream.append(self.stream)
+            return stream
+        else:
+            return super().to_string([f'//{self.name} DD '])
+
+
+@dataclass
+class COMMENT:
+    text: str = ''
+
+
+@dataclass
+class STEP(Base):
+    name: str
+    dd: list = field(default_factory=list)
+    _attrs = ('dd')
+
+    def to_string(self):
+        string = super().to_string([f'//{self.name} EXEC '])
+        for dd in self.dd:
+            string = string + dd.to_string()
+        print(string)
+        print('KIKO IKIKO IKIKO IKOKI')
+        return string
+
+
+class EXEC():
+
+    @dataclass
+    class EXEC(STEP):
+        acct: str = field(default=None, repr=False)
+        addrspc: str = field(default=None, repr=False)
+        ccsid: str = field(default=None, repr=False)
+        cond: str = field(default=None, repr=False)
+        dynamnbr: str = field(default=None, repr=False)
+        memlimit: str = field(default=None, repr=False)
+        parm: str = field(default=None)
+        parmdd: str = field(default=None, repr=False)
+        perform: str = field(default=None, repr=False)
+        pgm: str = field(default=None)
+        proc: str = field(default=None)
+        rd: str = field(default=None, repr=False)
+        region: str = field(default=None, repr=False)
+        rlstmout: str = field(default=None, repr=False)
+        time: str = field(default=None, repr=False)
+        steplib: DD = field(default=None)
+
+    def __new__(self, *args, **kwargs):
+        if len(args) == 2 or 'procname' in kwargs:
+            name = args[1] if len(args) == 2 else kwargs.pop('procname')
+            proc_parms = [(parm, str, field(default=None)) for parm in kwargs]
+            proc_class = make_dataclass(name, proc_parms, bases=(STEP,))
+            return proc_class(name, **kwargs)
+        else:
+            return EXEC.EXEC(*args, **kwargs)
+
+
+@dataclass()
+class JOB(UserList, Base):
+    name: str
+    _class: str = None
+    msgclass: str = None
+    notify: str = None
+    user: str = None
+    joblib: DD = None
+    typrun: str = None
+    time: str = None
+    restart: str = None
+    password: str = None
+    prty: int = field(default=None, repr=False)
+    perform: str = field(default=None, repr=False)
+    schenv: str = field(default=None, repr=False)
+    sysaff: str = field(default=None, repr=False)
+    ujobcorr: str = field(default=None, repr=False)
+    seclabel: str = field(default=None, repr=False)
+    rd: str = field(default=None, repr=False)
+    pages: str = field(default=None, repr=False)
+    lines: str = field(default=None, repr=False)
+    jobrc: str = field(default=None, repr=False)
+    jeslog: str = field(default=None, repr=False)
+    group: str = field(default=None, repr=False)
+    dsenqshr: str = field(default=None, repr=False)
+    cond: str = field(default=None, repr=False)
+    ccsid: str = field(default=None, repr=False)
+    cards: str = field(default=None, repr=False)
+    bytes: str = field(default=None, repr=False)
+    addrspc: str = field(default=None, repr=False)
+
+    _attrs = ('steps')
+
+    def __post_init__(self):
+        super().__init__()
+
+    def to_string(self):
+        stream = super().to_string([f'//{self.name} JOB '])
+        for statement in self.data:
+            stream.extend(statement.to_string())
+        print(stream)
+        return '\n'.join(stream)

          
A => pyproject.toml +16 -0
@@ 0,0 1,16 @@ 
+[tool.poetry]
+name = "pyjed"
+version = "0.1.0"
+description = ""
+authors = ["Your Name <you@example.com>"]
+
+[tool.poetry.dependencies]
+python = "^3.6"
+dataclasses = { version="^0.8", python = "~3.6"}
+
+[tool.poetry.dev-dependencies]
+pytest = "^5.2"
+
+[build-system]
+requires = ["poetry-core>=1.0.0"]
+build-backend = "poetry.core.masonry.api"

          
A => tests/test_pyjed.py +98 -0
@@ 0,0 1,98 @@ 
+#  import pytest
+from pyjed import __version__
+from pyjed import DD, EXEC, JOB
+
+
+def test_version():
+    assert __version__ == '0.1.0'
+
+
+def test_job():
+    JOB('MYJOB', _class='S')
+
+
+def test_serialize_job_one_line():
+    job = JOB('MYJOB', _class='S')
+    assert job.to_string() == '//MYJOB JOB CLASS=S'
+
+
+def test_serialize_job_two_lines():
+    job = JOB('MYJOB', _class='S', notify='&SYSUID', msgclass='V', time=34, user='JOHN',
+              schenv='SYS1', perform=244, prty=3)
+    assert job.to_string() == ('//MYJOB JOB CLASS=S,MSGCLASS=V,NOTIFY=&SYSUID,'
+                               'USER=JOHN,TIME=34,PRTY=3,\n//      PERFORM=244,'
+                               'SCHENV=SYS1')
+
+
+def test_serialize_job_with_step():
+    job = JOB('MYJOB', _class='S')
+    job.append(EXEC('STEP1', pgm='IEFBR14'))
+    assert job.to_string() == '//MYJOB JOB CLASS=S\n//STEP1 EXEC PGM=IEFBR14'
+
+
+def test_exec():
+    EXEC('STEP1')
+
+
+def test_exec_proc_arg():
+    proc = EXEC('STEP', 'PROC1', test=1, foo=2)
+    assert proc.test == 1 and proc.foo == 2
+
+
+def test_exec_proc_kwarg():
+    proc = EXEC('STEP', procname='PROC1', test=1, foo=2)
+    assert proc.test == 1 and proc.foo == 2
+
+
+def test_serialize_exec_one_line():
+    exec = EXEC('STEP1', pgm='IEFBR14')
+    assert exec.to_string() == ['//STEP1 EXEC PGM=IEFBR14']
+
+
+def test_serialize_exec_one_line_with_whitespace_parm():
+    exec = EXEC('STEP1', pgm='IEFBR14', parm='HELLO WORLD')
+    assert exec.to_string() == ['//STEP1 EXEC PARM=\'HELLO WORLD\',PGM=IEFBR14']
+
+
+def test_serialize_exec_one_line_with_lowercase_parm():
+    exec = EXEC('STEP1', pgm='IEFBR14', parm='hello world')
+    assert exec.to_string() == ['//STEP1 EXEC PARM=\'hello world\',PGM=IEFBR14']
+
+
+def test_serialize_exec_two_lines():
+    exec = EXEC('STEP1', pgm='IEFBR14', parm='Lorem ipsum dolor sit amet, consectetur '
+                                             'adipiscing elit')
+    assert exec.to_string() == ['//STEP1 EXEC PARM=\'Lorem ipsum dolor sit amet, '
+                                'consectetur adipiscing elit\',',
+                                '//      PGM=IEFBR14']
+
+
+def test_serialize_exec_proc():
+    proc = EXEC('STEP', procname='PROC1', test=1, foo=2)
+    proc.to_string()
+
+
+def test_dd_dsname():
+    DD('MYDD', dsname='MY.DATASET')
+
+
+def test_serialize_dd_dataset_one_line():
+    dd = DD('MYDD', dsname='MY.DATASET')
+    assert dd.to_string() == ['//MYDD DD DSNAME=MY.DATASET']
+
+
+def test_serialize_dd_instream():
+    dd = DD('MYDD', stream='Lorem ipsum dolor sit amet')
+    assert dd.to_string() == ['//MYDD DD *', 'Lorem ipsum dolor sit amet']
+
+
+def test_serialize_dd_dataset_two_lines():
+    dd = DD('MYDD', dsname='MY.LONG.LONG.LONG.DATASET', volume='SER',
+            disp='(,CATLG,DELETE)', dataclas='DCLAS02')
+    assert dd.to_string() == ['//MYDD DD DATACLAS=DCLAS02,DISP=(,CATLG,DELETE),',
+                              '//      DSNAME=MY.LONG.LONG.LONG.DATASET,VOLUME=SER']
+
+
+def test_serialize_dd_unixpath_one_line():
+    dd = DD('MYDD', path='/usr/applics/pay.time')
+    assert dd.to_string() == ['//MYDD DD PATH=\'/usr/applics/pay.time\'']