[ANN] rs 0.1

rs is a project to implement a non-traditional object-oriented shell in Ruby.
  
The three main features will be the simple power of Ruby as the shell scripting
language, an objectlike interface to files and object piping.

This is a preliminary release, mainly to re-introduce the project and
invite developers to join up.

=== Authors
See doc/AUTHORS.

=== Licence
See doc/LICENCE.

=== Communication
* IRC:
  ##rs on irc.freenode.net

* Mailing list:
  http://projects.kittensoft.org/mailman/listinfo/rs-dev_projects.kittensoft.org

=== Documentation
http://projects.kittensoft.org/rs/rdoc/index.html

=== Features
* Execution of arbitrary Ruby
* Pipes, input redirection
* FileSystemObjects which encapsulate paths and files

A development roadmap is at doc/TODO.roadmap

=== Download
==== Developers
Anyone interested in developing rs should read doc/HOWTO.development.

* darcs get http://repos.kittensoft.org/rs
* http://files.kittensoft.org/rs/rs-dev-0.1.tar.gz

==== Users
* http://files.kittensoft.org/rs/rs-0.1.tar.gz

=== Installation
It is advised that developers do not install the program locally at
this point but rather use bin/rs directly. Users may use the provided
setup.rb script:

  sudo ruby setup.rb all

=== Using
See doc/HOWTO.using.

=== Customizing
rs will load ~/.rsrc if it exists. This file is executed as a .rb and
can be used for defining methods, setting $config variables and even
modifying rs itself.

=== Current issues and bugs
The entire project is in flux. Think of it as an exploratory programming
experiment that will stretch all the way until 0.9 at which point the
real program will be extracted from the codebase at the time.

* Generally not production-ready.
* Cannot be used as a login shell.
* Inconsistent and incomplete internal model.
* The 'lexer' for determining completeness of Ruby statements is very weak.
* Tests, though relatively extensive, mainly ensure that the correct operation
  is present. Work is needed in testing for failure.

=== Statistic
The codebase is slightly under 500 lines with around 1750 lines of tests
(a 7:2 ratio) with 90+ specifications and 300+ assertions.

=== Dependencies
==== Bundled
* Test::Spec by Christian Neukirchen, http://chneukirchen.org. Developers only.
* FlexMock by Jim Weirich, http://onestepback.org/software/flexmock. Developers only.

==== Unbundled
* None

=== Special thanks
* Everyone who contributed to the original ruSH code.
* The Ruby hackers. I never appreciated the UNIXness of Ruby enough.

=== Copyright
Copyright (c) 2005-2006 by the Authors. All rights reserved.

Eero Saynatkari wrote:

rs is a project to implement a non-traditional object-oriented shell in Ruby.
  The three main features will be the simple power of Ruby as the shell scripting language, an objectlike interface to files and object piping.

This is a preliminary release, mainly to re-introduce the project and
invite developers to join up.

Different from ruSH? [1]
How so?

-Justin

[1]http://rubyforge.org/projects/rush/

"Eero Saynatkari" <ruby-ml@kittensoft.org> wrote in message
=== Documentation
http://projects.kittensoft.org/rs/rdoc/index.html

I love the doc/SPECS. Makes for very nice test/documentation!

rs is a project to implement a non-traditional object-oriented shell in Ruby.

And of course I forget to enter the most important part. Examples!

= Examples
Using rs.

== General notes
rs uses Readline which means that you can use the arrow
keys to go up and down in history and back and forth in
the current line.

== Executing Ruby code
You should be able to execute any Ruby code on the line:

  > 5 + 6
  => 11
  >

  > class Foo
  ..> def bar
  ..> puts 'Baz'
  ..> end
  ..> end
  => nil
  > Foo.new.bar
  Baz
  => nil
  >

== Output and environment control
You can affect the output using $config values of ruby_return,
prompt and continuation_prompt. $config (and $env) behave like
OpenStructs with the distinction that a method ending with ?
returns a boolean and one ending with ! will set the attribute
to true.

The prompts are #evaled so you can put arbitrary code in there.
Be mindful that a static string has to be enclosed in quotes for
it to work properly.

  > 1 + 1
  => 2
  > $config.ruby_return = false
  > 1 + 1
  > $config.ruby_return!
  => true
  >

  > $config.prompt
  => "'rs> '"
  > $config.prompt = '"#{Dir.pwd}> "'
  => '"#{Dir.pwd}> "'
  /tmp> class Foo
  ..> end
  /tmp> $config.continuation_prompt = $config.prompt
  => '"#{Dir.pwd}> "'
  /tmp> class Foo
  /tmp> end
  /tmp> $config.prompt = "'rs> '"; $config.continuation_prompt = "'..> '"

== FileSystemObjects
FSOs give a relatively object-like interface to files and paths
and incorporate several File, FileUtils, Dir etc. methods.

  > '/tmp'.to_fso.methods.sort
  => ["/", "<", "<<", "==", "===", "=~", ">", ">>", "__id__", "__send__",
    "append_to", "args", "basename", "blockdev?", "cat", "cd", "chardev?",
    "chmod", "chmod_R", "chown", "chown_R", "class", "clone", "compare",
    "cp", "cp_r", "directory?", "dirname", "display", "dup", "eql?", "equal?",
    "exec", "executable?", "executable_real?", "exist?", "exists?", "extend",
    "extname", "file?", "find", "freeze", "frozen?", "ftype", "glob", "grpowned?",
    "hash", "id", "inspect", "install", "instance_eval", "instance_of?",
    "instance_variable_get", "instance_variable_set", "instance_variables",
    "is_a?", "kind_of?", "ln", "ln_s", "ln_sf", "lstat", "method", "methods",
    "mkdir", "mkdir_p", "mv", "nil?", "object_id", "owned?", "path", "pipe",
    "pipe?", "private_methods", "protected_methods", "public_methods", "readable?",
    "readable_real?", "readlink", "relative_path", "respond_to?", "rm", "rm_r",
    "rm_rf", "rmdir", "run", "send", "setgid?", "setuid?", "singleton_methods",
    "size", "size?", "socket?", "split", "stat", "sticky?", "symlink", "symlink?",
    "taint", "tainted?", "to_a", "to_os", "to_s", "touch", "truncate", "type",
    "umask", "unlink", "untaint", "writable?", "writable_real?", "write_to", "zero?", "|"]
  >

Generally, these methods behave exactly as their Ruby counterparts with the path
of the FSO given as the file to operate on. For example:

  > '/tmp'.to_fso.directory?
  => true
  > '/tmp/quux'.to_fso.exist?
  => false
  > '/tmp'.to_fso.cd {'./quux'.to_fso.touch}
  => nil
  > '/tmp/quux'.to_fso.exist?
  => true
  > '/tmp/quux'.to_fso.rm
  => ['/tmp/quux']
  > '/tmp/quux'.to_fso.exist?
  => false

You could of course put the FSO in a variable to avoid the repetition--also, if
you feel like metaprogramming a bit, you could put a String#method_missing in your
~/.rsrc so that you can skip the #to_fso (which will eventually go away, of course).

== Executing programs
FSOs containing executable files may (unsurprisingly) be executed. One thing to
know about the processing of FSOs is that currently any filename that does not
start with ./, ../, / or ~/ is considered to be 'unqualified' and must exist in
$PATH. In addition to this, unknown methods at the top-level are first treated
as unqualified files (falling back on normal if not found). The UI provides
special handling and will automatically run executables. Arguments may also be
given.

  > 'ls'.to_fso.run
    ...
  => nil
  > ls
    ...
  => nil
  > ls '-la'
    ...
  => nil
  > 'ls'.to_fso.args('-l')
    ...
  => nil
  >

== Input redirection
More or less arbitrary objects can be 'redirected', > indicating overwriting
and >> appending. In both cases, the file will be created if it does not exist.

  > '/tmp/foo'.to_fso.touch
  => ["/tmp/foo"]
  > '/tmp/foo'.to_fso.cat
  => nil
  > '/tmp/foo'.to_fso < "Foo"
  => 3
  > '/tmp/foo'.to_fso.cat
  Foo
  => nil
  > '/tmp/foo'.to_fso << 45
  => 2
  > '/tmp/foo'.to_fso.cat
  Foo45
  => nil
  > '/tmp/nonexist'.to_fso < '/tmp/foo'.to_fso.read
  => 5
  > '/tmp/nonexist'.to_fso.cat
  Foo45
  => nil
  >

The opposite should also work:

  > '/tmp/nonexist'.to_fso.cat
  Foo45
  => nil
  > 78 >> '/tmp/nonexist'.to_fso
  => 2
  > '/tmp/nonexist'.to_fso.cat
  Foo4578
  => nil
  >

There are a few exceptions. If the 'input' is an Array, it is recursively
joined with newlines. If the input is an executable FSO, it will be run
and the result written as a String. Thirdly, if the file TO which the input
is going is executable, it is converted to an ObjectStream instead. This
brings us to our next topic.

== Pipes
Executable programs (and/or static input) can be chained together to
an arbitrary degree using ObjectStreams, also known as pipes.

The result of a piping operation can be queried with #result (this is
done automatically by the UI if the value of the expression is an OS).

Alternatively, an iterator interface is exposed with #each (an other
Enumerable methods).

A few modifications take place on a Ruby object being piped: Arrays are
newline-joined, #to_proc objects are #called and everything else is set
to its #to_s representation.

  > ls | wc
         8 8 46
  => nil
  > (ls | wc).result
  => [" 8 8 46"]
  > "foo\nbar" | wc('-l')
         2
  => nil
  > "foo\nbar" | wc('-l')
         2
  => nil
  > (ls | tr('a-z A-Z')).each {|f| p f.reverse}
  "ELIFEKAR"
  "SCRAD_"
  "NIB"
  "OOB"
  "COD"
  "BIL"
  "BR.PUTES"
  "TSET"
  => #<IO:0x65be40>
  > lambda {"foo\nbar"} | wc
         2 2 8
  => nil

···

On 2006.10.12 12:38, Eero Saynatkari wrote:
  >

It is different in that I scrapped the code in ruSH and started over.
The ideas remain broadly the same, currently with the benefit of not
having a Curses interface which simplifies trying it out.

The approach is a bit different this time, starting from working TDD.

···

On 2006.10.12 14:06, Justin Collins wrote:

Eero Saynatkari wrote:
>rs is a project to implement a non-traditional object-oriented shell in
>Ruby.
>
>The three main features will be the simple power of Ruby as the shell
>scripting language, an objectlike interface to files and object piping.
>
>This is a preliminary release, mainly to re-introduce the project and
>invite developers to join up.
>
>

Different from ruSH? [1]
How so?