Giter Site home page Giter Site logo

marcel's Introduction

What's New

You can assign the value of a Python expression to a variable, e.g.

x = (5 + 6)

assigns 11 to the variable x. The RHS, (5 + 6) is shorthand for (lambda: 5 + 6). In other words, the function inside the parens is invoked, and the resulting value is assigned.

Sometimes you want to assign a function to a variable. Following the previous example, to assign to inc a function that adds 1 to its input, you would have to do something like this:

inc = (lambda: lambda x: x + 1)

Evaluating (lambda: lambda x: x + 1) yields the function lambda x: x + 1 which is assigned to inc. That is kind of clunky, and while obviously correct and consistent, it isn't what you normally think of first. Or at least I didn't, I tried inc = (lambda x: x + 1) and got an error message because (lambda x: x + 1) cannot be evaluated without binding a value to x.

In this release, the less clunky, less consistent syntax is permitted. If you write:

inc = (lambda x: x + 1)

then inc will be assigned lambda x: x + 1. I.e., it will be understood that you really meant inc = (lambda: lambda x: x + 1). This only happens when assigning expressions to variables, and the parenthesized function has more than zero arguments. In other contexts, (e.g. the function used with map or select), there is no similar tweaking of the expression.

Marcel

Marcel is a shell. The main idea is to rely on piping as the primary means of composition, as with any Unix or Linux shell. However, instead of passing strings from one command to the next, marcel passes Python values: builtin types such as lists, tuples, strings, and numbers; but also objects representing files and processes.

Linux has extremely powerful commands such as awk and find. Most people know how to do a few simple operations using these commands. But it is not easy to exploit the full power of these commands due to their reliance on extensive "sublanguages" which do:

  • Filtering: What data is of interest?
  • Processing: What should be done with the data?
  • Formatting: How should results be presented?

By contrast, marcel has no sublanguages. You use marcel operators combined with Python code to filter data, process it, and control command output.

The commands and syntax supported by a shell constitute a language which can be used to create scripts. Of course, in creating a script, you rely on language features that you typically do not use interactively: control structures, data types, and abstraction mechanisms (e.g. functions), for example. Viewed as a programming language, shell scripting languages are notoriously bad. I didn't think it was wise to bring another one into the world. So marcel takes a different approach, using Python as a scripting language, (see below for more on scripting).

Pipelines

Marcel provides commands, called operators, which do the basic work of a shell. An operator takes a stream of data as input, and generates another stream as output. Operators can be combined by pipes, causing one operator's output to be the next operator's input. For example, this command uses the ls and map operators to list the names and sizes of files in the /home/jao directory:

ls /home/jao | map (lambda f: (f, f.size))
  • The ls operator produces a stream of File objects, representing the contents of the /home/jao directory.
  • | is the symbol denoting a pipe, as in any Linux shell.
  • The pipe connects the output stream from ls to the input stream of the next operator, map.
  • The map operator applies a given function to each element of the input stream, and writes the output from the function to the output stream. The function is enclosed in parentheses. It is an ordinary Python function, except that the keyword lambda is optional. In this case, an incoming File is mapped to a tuple containing the file and the file's size.

A pipeline is a sequence of operators connected by pipes. They can be used directly on the command line, as above. They also have various other uses in marcel. For example, a pipeline can be assigned to a variable, essentially defining a new operator. For example, here is a pipeline, assigned to the variable recent, which selects Files modified within the past day:

recent = (| select (file: now() - file.mtime < days(1)) |) 
  • The pipeline being defined is bracketed by (|...|). (Without the brackets, marcel would attempt to evaluate the pipeline immediately, and then complain because the parameter file is not bound.)
  • The pipeline contains a single operator, select, which uses a function to define the items of interest. In this case, select operates on a File, bound to the parameter file.
  • now() is a function defined by marcel which gives the current time in seconds since the epoch, (i.e., it is just time.time()).
  • File objects have an mtime property, providing the time since the last content modification.
  • days() is another function defined by marcel, which simply maps days to seconds, i.e., it multiplies by 24 * 60 * 60.

This pipeline can be used in conjunction with any pipeline yielding files. E.g., to locate the recently changed files in ~/git/myproject:

ls ~/git/myproject | recent

Functions

As shown above, a number of operators, like map and select, take Python functions as command-line arguments. Functions can also be invoked to obtain the value of an environment variable. For example, to list the contents of your home directory, you could write:

ls /home/(USER)

This concatenates the string /home/ with the string resulting from the evaluation of the expression lambda: USER. USER is a marcel environment variable identifying the current user, (so this command is equivalent to ls ~).

If you simply want to evaluate a Python expression, you could use the map operator, e.g.

map (5 + 6)

which prints 11. Marcel permits the map operator to be inferred, so this also works:

(5 + 6)

In general, you can elide map from any pipeline.

Executables

In addition to using built-in operators, you can, of course, call any executable. Pipelines may contain a mixture of marcel operators and host executables. Piping between operators and executables is done via streams of strings.

For example, this command combines operators and executables. It scans /etc/passwd and lists the usernames of users whose shell is /bin/bash. cat, xargs, and echo are Linux executables. map and select are marcel operators. The output is condensed into one line through the use of xargs and echo.

cat /etc/passwd \
| map (line: line.split(':')) \
| select (*line: line[-1] == '/bin/bash') \
| map (user, *_: user) \
| xargs echo
  • cat /etc/passwd: Obtain the contents of the file. Lines are piped to subsequent commands.
  • map (line: line.split(':')): Split the lines at the : separators, yielding 7-tuples.
  • select (*line: line[-1] == '/bin/bash'): select those lines in which the last field is /bin/bash.
  • map (user, *_: user) |: Keep the username field of each input tuple.
  • xargs echo: Combine the incoming usernames into a single line, which is printed to stdout.

Shell Features

Marcel provides:

  • Command history: A history operator, rerunning and editing of previous commands, reverse search, etc.
  • Customizable prompts: Configured in Python, of course.
  • Tab completion: For operators, flags, and filename arguments.
  • Help: Extensive help facility, providing information on concepts, objects, and operators.
  • Customizable color highlighting: The colors used to render output for builtin types such as File and Process, and help output can be customized too.
  • Dynamic reconfiguration: Changes to configuration and startup scripts are picked up without restarting.

Scripting

Marcel's syntax for constructing and running pipelines, and defining and using variables and functions, was designed for interactive usage. Instead of extending this syntax to a full-fledged scripting language, marcel provides a Python API, allowing Python to be used as the scripting language. While Python is sometimes considered to already be a scripting language, it isn't really. Executing shell commands from Python code is cumbersome. You've got to use os.system, or subprocess.Popen, and write some additional code to do the integration.

Marcel provides a Python module, marcel.api, which brings shell commands into Python in a much cleaner way. For example, to list file names and sizes in /home/jao:

from marcel.api import *

for file, size in ls('/home/jao') | map(lambda f: (f, f.size)):
    print(f'{file.name}: {size}') 

This code uses the ls and map functions, provided by marcel.api. These correspond to the marcel operators ls and map that you can use on the command line. Output from the ls is a stream of Files, which are piped to map, which maps files to (file, file size) tuples. ls ... | map ... defines a pipeline (just as on the command line). The Python class representing pipelines defines iter, so that the pipeline's output can be iterated over using the standard Python for loop.

Installation

To install marcel locally (i.e., available only to your username):

python3 -m pip install marcel

This command installs marcel for the current user. To install for the entire system, use sudo python3 -m pip install --prefix ... instead. (The value of the --prefix flag should be something like /usr/local.)

Marcel depends on dill and psutil. These packages will be installed automatically if needed, when marcel is installed via pip.

marcel's People

Contributors

da4089 avatar geophile avatar jiripospisil avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

marcel's Issues

Example request: arguments

The pipeline example shows how to use data items passed by the previous part of the pipeline, but doesn't allow specifying arguments:

$ l = (|ls|)
$ l foo
Wrong number of arguments for pipeline l = ls

This makes creating custom commands limited.

The bash command crashes the shell

/home/o $ bash
Traceback (most recent call last):
  File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/home/o/.local/lib/python3.10/site-packages/marcel/main.py", line 314, in <module>
    main()
  File "/home/o/.local/lib/python3.10/site-packages/marcel/main.py", line 297, in main
    MAIN.run(print_prompt)
  File "/home/o/.local/lib/python3.10/site-packages/marcel/main.py", line 131, in run
    self.run_command(self.input)
  File "/home/o/.local/lib/python3.10/site-packages/marcel/main.py", line 143, in run_command
    pipeline = parser.parse()
  File "/home/o/.local/lib/python3.10/site-packages/marcel/parser.py", line 948, in parse
    return self.command()
  File "/home/o/.local/lib/python3.10/site-packages/marcel/parser.py", line 954, in command
    command = self.pipeline()
  File "/home/o/.local/lib/python3.10/site-packages/marcel/parser.py", line 1034, in pipeline
    pipeline.append(self.create_op(*op_args))
  File "/home/o/.local/lib/python3.10/site-packages/marcel/parser.py", line 1149, in create_op
    op = self.create_op_builtin(op_token, arg_tokens)
  File "/home/o/.local/lib/python3.10/site-packages/marcel/parser.py", line 1182, in create_op_builtin
    op_module.args_parser().parse(args, op)
  File "/home/o/.local/lib/python3.10/site-packages/marcel/op/bash.py", line 62, in parse
    if args[0] in ('-i', '--interactive'):
IndexError: list index out of range
~ $ 

Now after the IndexError the shell is back to bash. How to exit the marcel shell gracefully? exit or quit do not work ...

Bug: spaceful path handling

$ ls /mnt/space/games/Cryostasis\ 2.0.0.13\ [GOG]/
No qualifying paths, (possibly due to permission errors): ['/mnt/space/games/Cryostasis 2.0.0.13 [GOG]/']

Meanwhile, bash can list it just fine.

Getting the path through tab-completion does not escape the spaces, either.

marcel doesn't run ipython

Screenshot from 2023-11-22 11-21-58

Tried out marcel, some of it's features I liked, some issues I think could use some work.

One issue is I could not run iPython in my shell once I invoked marcel.

Missing tags

Hello, I'm creating a package for Arch Linux AUR and I've noticed this repository is missing the Git tag for the latest version (0.18.3 as of now). These tags are handy for packagers because GitHub creates permanent URLs for them.

New behavior for existing standard-shell commands?

I am exploring marcel and I see that existing commands have different behavior in marcel.
Most of us have ingrained muscle memory with regular shell commands (like I always do "ls -ltr").

Is this by design or are these bugs that need to be fixed?

for e.g.

(with bash)

(venv) bhakta@blr:~/dev/marcel$ ls -fr
.  ..  .git  .gitignore  .idea  COPYING  LICENSE  README.md  bin  experiments  marcel  notes  setup.py  test  venv
(venv) bhakta@blr:~/dev/marcel$ ./bin/marcel

(with marcel)


/home/bhakta/dev/marcel> ls -fr | less
Missing filename ("less --help" for help)

/home/bhakta/dev/marcel>
/home/bhakta/dev/marcel> ls -fr > x
/home/bhakta/dev/marcel> vim x

<hangs - I had to ctrl-c>
^C

Ctrl-c did not come out, I had to kill the window


/home/bhakta/dev/marcel> ls -ltr
Operator ls: Unknown flag -ltr
/home/bhakta/dev/marcel>

Community ?

Hi,

Thanks for marcel, though I think it'd deserve some more acknoledgment
Maybe you should have an irc/matrix/discord/* chan somewhere ?
have a nice day

Problem with executing bash commands ( bash cat $(which marcel) )

Hmmm ... why is it a problem to pass shell commands to bash executing bash -c "any command, also multi-line" and to mirror bash output to the Terminal ??? General flaw in the approach of parsing the input line ?

~ $ cat $(which marcel)
#!/bin/bash

function suppress_interruptions()
{
  return 0
}

trap suppress_interruptions SIGINT
trap suppress_interruptions SIGTSTP

python3 -m marcel.main $*
~ $ 
~ $ dash
$ bash -c "cat $(which marcel)"
#!/bin/bash

function suppress_interruptions()
{
  return 0
}

trap suppress_interruptions SIGINT
trap suppress_interruptions SIGTSTP

python3 -m marcel.main $*
$ 
/home/o $ bash cat $(which marcel)
Invalid function syntax: f'''${which marcel}'''
/home/o $ bash "cat $(which marcel)"
Error: ['/bin/bash: line 1: cat /home/o/.local/bin/marcel: No such file or directory']
/home/o $ bash -c "cat $(which marcel)"
Error: ['/bin/bash: - : invalid option']
Error: ['Usage:\t/bin/bash [GNU long option] [option] ...']
Error: ['\t/bin/bash [GNU long option] [option] script-file ...']
Error: ['GNU long options:']
Error: ['\t--debug']
Error: ['\t--debugger']
Error: ['\t--dump-po-strings']
Error: ['\t--dump-strings']
Error: ['\t--help']
Error: ['\t--init-file']
Error: ['\t--login']
Error: ['\t--noediting']
Error: ['\t--noprofile']
Error: ['\t--norc']
Error: ['\t--posix']
Error: ['\t--pretty-print']
Error: ['\t--rcfile']
Error: ['\t--restricted']
Error: ['\t--verbose']
Error: ['\t--version']
Error: ['Shell options:']
Error: ['\t-ilrsD or -c command or -O shopt_option\t\t(invocation only)']
Error: ['\t-abefhkmnptuvxBCHP or -o option']
/home/o $ 

Needs Nix(OS) package

AFAICT there's currently no package for this lovely little shell in Nix (and therefore, NixOS.)

Generally making .nix packages from Python packages is fairly trivial, so I'm going to try later this week.

If anyone wants to beat me to it, go ahead. :D

Missing GitHub releases

Hello, I've noticed the last few releases (tags) are missing their corresponding GitHub releases. These are useful because I can then create a custom Watch on the repository and get notified when a new release is available (I package marcel for Arch Linux AUR). No need to create them retroactively, just for the latest one and then moving forward. Thank you!

Problem with `bash ls -i` ( -l option is OK, -i option fails )

I am currently working towards a concept of user interaction with the system ( https://github.com/oOosys/oOo ) which seems to share some of main thoughts with the marcel approach to shell scripting.

Reading about marcel shell I had thought I have understood the approach the right way, but it seems I haven't:

~ $ echo $0
bash
~ $ ls -i *.txt
9311566 tmp.txt
/home/o $ bash ls -l *.txt
-rw-rw-r-- 2 o o 101 2023-09-16 18:49 tmp.txt
/home/o $ bash ls -i *.txt
Operator bash: Flags must all appear before the first anonymous arg
/home/o $ version
0.18.4
/home/o $ ls *.txt
-rw-rw-r--   o      o           101   2023 Sep 16 18:49:37 tmp.txt
/home/o $ ls -i *.txt
Operator ls: Unknown flag -i
/home/o $

I have thought that the bash operator will take the shell command as it is and pass it to the bash shell ...

Any hints towards better understanding why I have failed to obtain the inode value for the file? Or have I run into a bug?

Claudio ( https://github.com/oOosys/oOo )

Root path `/` is converted to a list of root folders

Quite a simple one, but cd / fails with Too many paths.
Related to the same bug, ls / lists all root folders contents.

Given how op.Filenames.normalize method is built, I would just return [path] if path is root folder, but I'm not sure how do you prefer to handle it.

Also, can I ask what's your policy on releases? The last release is 0.22.2

marcel startup fails

On Ubuntu 22.04 with Python 3.11.6 in a virtual environment:

python3 -mvenv venv
. ./venv/bin/activate
pip install marcel
$ marcel

Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/home/nkabir/venv/lib/python3.11/site-packages/marcel/main.py", line 317, in <module>
    main()
  File "/home/nkabir/venv/lib/python3.11/site-packages/marcel/main.py", line 293, in main
    MAIN = Main(None, same_process=False, old_namespace=old_namespace)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/nkabir/venv/lib/python3.11/site-packages/marcel/main.py", line 107, in __init__
    self.initialize_reader()  # Sets self.reader
    ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/nkabir/venv/lib/python3.11/site-packages/marcel/main.py", line 177, in initialize_reader
    self.reader = Reader(self.env, self.env.locations.history_path())
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/nkabir/venv/lib/python3.11/site-packages/marcel/main.py", line 58, in __init__
    super().__init__(history_file=history_file)
  File "/home/nkabir/venv/lib/python3.11/site-packages/marcel/multilinereader.py", line 46, in __init__
    self._fix_history()
  File "/home/nkabir/venv/lib/python3.11/site-packages/marcel/multilinereader.py", line 115, in _fix_history
    readline.read_history_file(self.history_file)
OSError: [Errno 22] Invalid argument

Too many errors from stdin

Ubuntu: 18.04.4
Python3: 3.6.9
Marcel: 0.12.2

Starting nano is not possible (with or without file argument). -> Results in "Too many errors from stdin"
Runs in bash.

Pickle failure

So, trying to run anything in Marcel appears to cause the following error:

laptop> marcel
$ print('a')
Traceback (most recent call last):
File "/opt/local/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/runpy.py", line 193, in _run_module_as_main
return _run_code(code, main_globals, None,
File "/opt/local/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/runpy.py", line 86, in _run_code
exec(code, run_globals)
File "/Users/d/work/third-party/marcel-play/venv38/lib/python3.8/site-packages/marcel/main.py", line 209, in
MAIN.run()
File "/Users/d/work/third-party/marcel-play/venv38/lib/python3.8/site-packages/marcel/main.py", line 116, in run
self.run_command(line)
File "/Users/d/work/third-party/marcel-play/venv38/lib/python3.8/site-packages/marcel/main.py", line 137, in run_command
self.job_control.create_job(command)
File "/Users/d/work/third-party/marcel-play/venv38/lib/python3.8/site-packages/marcel/job.py", line 235, in create_job
job = Job(self.env, command)
File "/Users/d/work/third-party/marcel-play/venv38/lib/python3.8/site-packages/marcel/job.py", line 77, in init
self.start_process()
File "/Users/d/work/third-party/marcel-play/venv38/lib/python3.8/site-packages/marcel/job.py", line 168, in start_process
self.process.start()
File "/opt/local/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/multiprocessing/process.py", line 121, in start
self._popen = self._Popen(self)
File "/opt/local/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/multiprocessing/context.py", line 224, in _Popen
return _default_context.get_context().Process._Popen(process_obj)
File "/opt/local/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/multiprocessing/context.py", line 283, in _Popen
return Popen(process_obj)
File "/opt/local/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/multiprocessing/popen_spawn_posix.py", line 32, in init
super().init(process_obj)
File "/opt/local/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/multiprocessing/popen_fork.py", line 19, in init
self._launch(process_obj)
File "/opt/local/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/multiprocessing/popen_spawn_posix.py", line 47, in _launch
reduction.dump(process_obj, fp)
File "/opt/local/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/multiprocessing/reduction.py", line 60, in dump
ForkingPickler(file, protocol).dump(obj)
AttributeError: Can't pickle local object 'Job.start_process..run_command'
laptop>

I'm not sure where to start debugging that. Any hints?

macOS 10.14.6 / Python 3.8.2

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.