Skip to content

Commit

Permalink
[builtin/jobs] Refactor so it can be translated.
Browse files Browse the repository at this point in the history
We still need to print the source code like every other shell does.
Moreoever they actually join lines and insert semi-colons in code like
this:

    { echo one
      echo two
    } | tac | wc -l

Thinking about how to display debug info to track down #1002 as well.
  • Loading branch information
Andy C committed Nov 17, 2021
1 parent cd9786e commit 8139941
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 32 deletions.
81 changes: 58 additions & 23 deletions core/process.py
Expand Up @@ -37,7 +37,7 @@
from osh import cmd_eval
from qsn_ import qsn
from mycpp import mylib
from mycpp.mylib import tagswitch, iteritems
from mycpp.mylib import tagswitch, iteritems, NewStr

import posix_ as posix
from posix_ import (
Expand Down Expand Up @@ -778,6 +778,10 @@ def __init__(self):
# Initial state with & or Ctrl-Z is Running.
self.state = job_state_e.Running

def DisplayJob(self, job_id, f):
# type: (int, mylib.Writer) -> None
raise NotImplementedError()

def State(self):
# type: () -> job_state_t
return self.state
Expand Down Expand Up @@ -833,8 +837,22 @@ def Init_ParentPipeline(self, pi):

def __repr__(self):
# type: () -> str
s = ' %s' % self.parent_pipeline if self.parent_pipeline else ''
return '<Process %s%s>' % (self.thunk, s)

# note: be wary of infinite mutual recursion
#s = ' %s' % self.parent_pipeline if self.parent_pipeline else ''
#return '<Process %s%s>' % (self.thunk, s)
return '<Process %s>' % self.thunk

def DisplayJob(self, job_id, f):
# type: (int, mylib.Writer) -> None
if job_id == -1:
job_id_str = ' '
else:
job_id_str = '%%%d' % job_id
f.write('%s %d %7s ' % (job_id_str, self.pid, _JobStateStr(self.state)))
# TODO:
f.write(self.thunk.DisplayLine())
f.write('\n')

def AddStateChange(self, s):
# type: (ChildStateChange) -> None
Expand Down Expand Up @@ -976,8 +994,16 @@ def __repr__(self):
parts.append('>\n')
return ''.join(parts)

def DisplayLine(self):
return 'Pipeline %s\n' % self.procs
def DisplayJob(self, job_id, f):
# type: (int, mylib.Writer) -> None
for i, proc in enumerate(self.procs):
if i == 0: # show job ID for first element in pipeline
job_id_str = '%%%d' % job_id
else:
job_id_str = ' ' # 2 spaces

f.write('%s %d %7s ' % (job_id_str, proc.pid, _JobStateStr(proc.state)))
f.write('TODO\n')

def Add(self, p):
# type: (Process) -> None
Expand Down Expand Up @@ -1153,7 +1179,7 @@ def WhenDone(self, pid, status):

def _JobStateStr(i):
# type: (job_state_t) -> str
return job_state_str(i)[10:] # remove 'job_state.'
return NewStr(job_state_str(i))[10:] # remove 'job_state.'


class JobState(object):
Expand Down Expand Up @@ -1238,7 +1264,7 @@ def JobFromPid(self, pid):
"""
return self.child_procs.get(pid)

def List(self):
def DisplayJobs(self):
# type: () -> None
"""Used by the 'jobs' builtin.
Expand All @@ -1254,7 +1280,13 @@ def List(self):
# echo hi | wc -l & -- this starts a process which starts two processes
# Wait for ONE.
#
# bash GROUPS the PIDs by job. And it has their state and code.
# 'jobs -l' GROUPS the PIDs by job. It has the job number, + - indicators
# for %% and %-, PID, status, and "command".
#
# Every component of a pipeline is on the same line with 'jobs', but
# they're separated into different lines with 'jobs -l'.
#
# See demo/jobs-builtin.sh

# $ jobs -l
# [1]+ 24414 Stopped sleep 5
Expand All @@ -1265,25 +1297,28 @@ def List(self):
# [3]- 24508 Running sleep 6
# 24509 | sleep 6
# 24510 | sleep 5 &
#
# zsh has VERY similar UI.

# NOTE: Jobs don't need to show state? Because pipelines are never stopped
# -- only the jobs within them are.
# TODO:
# - don't use __repr__ and so forth
# - jobs -l shows one line per PROCESS in a pipeline
# - We shouldn't show processes in 'jobs' or 'jobs -l'.
# - maybe we need an extension 'jobs --all' or 'jobs --history'

if mylib.PYTHON:
# TODO: don't use __repr__ and so forth
f = mylib.Stdout()
for job_id, job in iteritems(self.jobs):
# Use the %1 syntax
job.DisplayJob(job_id, f)

print('Jobs:')
for job_id, job in iteritems(self.jobs):
# Use the %1 syntax
print('%%%d %7s %s' % (job_id, _JobStateStr(job.State()), job))
def DisplayDebug(self):
# type: () -> None

print('')
print('Processes:')
for pid, proc in iteritems(self.child_procs):
p = ' |' if proc.parent_pipeline else ''
print('%d %7s %s%s' % (pid, _JobStateStr(proc.state), proc.thunk.DisplayLine(), p))
f = mylib.Stdout()
f.write('\n')
f.write('[debug info]\n')
for pid, proc in iteritems(self.child_procs):
proc.DisplayJob(-1, f)
#p = ' |' if proc.parent_pipeline else ''
#print('%d %7s %s%s' % (pid, _JobStateStr(proc.state), proc.thunk.DisplayLine(), p))

def ListRecent(self):
# type: () -> None
Expand Down
38 changes: 35 additions & 3 deletions demo/jobs-builtin.sh
Expand Up @@ -30,8 +30,12 @@ show_jobs() {
echo ___

# all shells support this long format which shows components of a pipeline
jobs -l
#jobs
#if true; then
if true; then
jobs -l
else
jobs
fi
}

myfunc() {
Expand All @@ -47,7 +51,13 @@ demo() {
sleep 1 & sleep 2 &
show_jobs

{ echo pipe1; sleep 0.5; } | cat &
# In jobs -l, bash, zsh, and mksh all combine this onto one line:
# { echo pipe1; sleep 0.5; }

{ echo pipe1
sleep 0.5
} | tac | wc -l &

show_jobs

myfunc &
Expand All @@ -65,4 +75,26 @@ demo() {
show_jobs
}

many_jobs() {
sleep 0.90 &
sleep 0.91 &
sleep 0.92 &
sleep 0.93 &
sleep 0.94 &
sleep 0.95 &
sleep 0.96 &
sleep 0.97 &
sleep 0.98 &
sleep 0.99 &

show_jobs

# Testing this syntax
# Doesn't work because shells say "job %10 not created under job control"
# fg %10

wait
show_jobs
}

"$@"
4 changes: 4 additions & 0 deletions frontend/flag_def.py
Expand Up @@ -172,6 +172,10 @@
TRAP_SPEC.ShortFlag('-p')
TRAP_SPEC.ShortFlag('-l')

JOB_SPEC = FlagSpec('jobs')
JOB_SPEC.ShortFlag('-l', help='long format')
JOB_SPEC.LongFlag('--debug', help='display debug info')

#
# FlagSpecAndMore
#
Expand Down
12 changes: 6 additions & 6 deletions osh/builtin_process.py
Expand Up @@ -223,14 +223,14 @@ def __init__(self, job_state):
def Run(self, cmd_val):
# type: (cmd_value__Argv) -> int

# NOTE: the + and - in the jobs list mean 'current' and 'previous', and are
# addressed with %+ and %-.
attrs, arg_r = flag_spec.ParseCmdVal('jobs', cmd_val)
arg = arg_types.jobs(attrs.attrs)

# [6] Running sleep 5 | sleep 5 &
# [7]- Running sleep 5 | sleep 5 &
# [8]+ Running sleep 5 | sleep 5 &
# note: we always use 'jobs -l' format, so -l is a no-op
self.job_state.DisplayJobs()
if arg.debug:
self.job_state.DisplayDebug()

self.job_state.List()
return 0


Expand Down

0 comments on commit 8139941

Please sign in to comment.