Intention of this framework is to bring an object-oriented programming paradigm to the Bash language.
DISCLAIMER
Experimental Research & Development project!
No backward compatibility is guaranteed!
Use it at your own risk!
There are many mature object-oriented programming languages on the market. If an OOP language is needed, Bash and the BeSharp Framework may not be the best choice in comparison to languages like Java or Python.
Bash code is typically used for small procedural scripts.
However, sometimes Bash scripts can become larger as new requirements arise. It is not always clear when it is appropriate to abandon the growth of Bash scripts and rewrite them in a modern language from scratch.
Sometimes, introducing a modern programming language(s) to workstations, along with all necessary dependencies, can be a challenging task. Bash is often used in this context because it is available on many Unix workstations out of the box.
What if we could write apt-get install
-related logic in an OOP manner?
What if Bash supported OOP techniques from the beginning?
Would shell scripts have survived their growth?
BeSharp Framework is trying to answer for these questions.
If you have Docker available on your workstation, please run the demo showcase app:
docker run -it --rm mmmostrowski/besharp-mvc-hello-world
π Visit the demo app project page for further instructions π
- classes, objects, fields, methods, semi-static classes,
- interfaces, inheritance, abstract methods, abstract classes,
- $this, object pointers mechanics, oop-like syntax,
- returning values from methods,
- parent methods calling,
- setters & getters overriding,
- objects cloning,
- looping && iterators,
- built-in dependency injection, automatic *Factory classes,
- system of collections:
- vectors,
- lists,
- maps,
- sets,
- queues,
- stacks,
- priority queues & pairing heaps,
- compiler and runtime for *.be.sh files,
- different app linking and building strategies,
- ability for plugins.
Below is an example code of a primitive CLI arguments parsing mechanism.
The Arguments
class might be an OOP replacement for the Bash getopt
command.
#!/usr/bin/env bash
@class AppEntrypoint @implements Entrypoint
function AppEntrypoint.main()
{
@let arguments = $this.makeArguments "${@}"
if @true $arguments.valueOf isAskingForHelp; then
(
echo ''
echo 'An example Hello-World App on BeSharp Framework.'
echo ''
echo 'Usage:'
$arguments.printUsage
echo ''
) >&2
return 0
fi
@let greetingText = $this.renderGreeting $arguments
if @true $arguments.valueOf isLoud; then
echo "$( @fmt bold )${greetingText}$( @fmt reset )"
else
echo "${greetingText}"
fi
}
function AppEntrypoint.renderGreeting() {
local arguments="${1}"
@returning "$( @ $arguments.valueOf greeting ) $( @ $arguments.valueOf subject )$( @ $arguments.valueOf suffix )"
}
function AppEntrypoint.makeArguments()
{
@let arguments = @new Arguments "${@}"
$arguments.add greeting \
'--greeting' '-g' \
'The way you want to greet. Default greeting is: ' 'Hello'
$arguments.add subject \
'--subject' '-s' \
'The subject you want to greet. Default subject is: ' 'World'
$arguments.add suffix \
'--suffix' '-u' \
'The string being placed at the end. Default is: ' '.'
$arguments.addFlag isLoud \
'--loud' '-l' \
'Makes bold output.' 'false'
$arguments.addFlag isAskingForHelp \
'--help' '-h' \
'Show help.' 'false'
$arguments.process
@returning $arguments
}
@classdone
@class Arguments
@var Map arguments
@var Vector inputArgs
@var Map inputValues
@var Vector argsInOrder
function Arguments()
{
@let $this.inputArgs = @vectors.make "${@}"
@let $this.inputValues = @maps.make
@let $this.arguments = @maps.make
@let $this.argsInOrder = @vectors.make
}
function Arguments.add()
{
$this.createArgument false "${@}"
}
function Arguments.addFlag()
{
$this.createArgument true "${@}"
}
function Arguments.process()
{
$this.initDefaultValues
@let inputValues = $this.inputValues
local itemIsValue=false
while @iterate @of $this.inputArgs @in argument; do
if $itemIsValue; then
$inputValues.set "${key}" "${argument}"
itemIsValue=false
continue
fi
@let argument = $this.findArgument "${argument}"
@let key = $argument.key
if @true $argument.isFlag; then
$inputValues.set "${key}" true
else
itemIsValue=true
fi
done
if $itemIsValue; then
local argText
argText="$( @ $argument.longName ) ($( @ $argument.shortName ))"
besharp.error "The value is missing for ${argText} argument!"
fi
}
function Arguments.valueOf()
{
local key="${1}"
@let inputValues = $this.inputValues
@returning @of $inputValues.get "${key}"
}
function Arguments.printUsage()
{
local padding=19
@let arguments = $this.arguments
while @iterate @of $this.argsInOrder @in argument; do
local paddingText=''
local totalString="$( @ $argument.longName )$( @ $argument.shortName )"
local paddingSize=$(( padding - ${#totalString} ))
while (( --paddingSize >= 0 )); do
paddingText+=' '
done
echo -n " "
if @true $argument.isFlag; then
echo -n "$(@ $argument.shortName), $(@ $argument.longName) ${paddingText} - "
@echo $argument.description
else
echo -n "$(@ $argument.shortName) value, $(@ $argument.longName) value ${paddingText} - "
echo "$(@ $argument.description)$(@fmt bold)$(@ $argument.defaultValue)$(@fmt reset)"
fi
done
}
function Arguments.createArgument()
{
@let argument = @new Argument
$argument.isFlag = "${1}"
$argument.key = "${2}"
$argument.longName = "${3}"
$argument.shortName = "${4}"
$argument.description = "${5}"
$argument.defaultValue = "${6}"
@let argsMap = $this.arguments
$argsMap.set "${2}" $argument
@let argsVector = $this.argsInOrder
$argsVector.add $argument
}
function Arguments.findArgument()
{
local name="${1}"
while @iterate @of $this.arguments @in argument; do
if @returned @of $argument.longName == "${name}" \
|| @returned @of $argument.shortName == "${name}"; then
@returning $argument
return
fi
done
besharp.error "Invalid argument: ${argument}!"
}
function Arguments.initDefaultValues()
{
@let inputValues = $this.inputValues
while @iterate @of $this.arguments @in argument; do
@let key = $argument.key
@let defaultValue = $argument.defaultValue
$inputValues.set "${key}" "${defaultValue}"
done
}
@classdone
@class Argument
@var key
@var longName
@var shortName
@var description
@var defaultValue
@var isFlag
@classdone
Example usage ( see below how to run ):
develop --greeting "How are you" -s "programmer" --suffix '?'
Target compiled executable script for this example can be found here.
The framework is mainly composed of two components:
- Compiler - transforms BeSharp OOP code into *.be.sh distribution files,
- Runtime - executes BeSharp *.be.sh files on a native Bash 4.4+.
These components can be combined into a single shell script or separated, depending on the chosen building preset.
There are four default building presets available for your app:
single-script
(default)- compiles your app code and BeSharp Runtime code into a single *.sh script,
- the client workstation is not required to have the BeSharp Runtime installed,
- all code is loaded at once during start,
single-script-noruntime
- compiles your app code into a single *.sh, but without including the BeSharp Runtime,
- the client workstation is required to have the BeSharp Runtime installed,
- all code is loaded at once during start,
multi-file
- compiles your app into a series of *.be.sh files, including the BeSharp Runtime *.be.sh file,
- the client workstation is not required to have the BeSharp Runtime installed,
- code is loaded dynamically, "when needed" by the app runtime,
multi-file-noruntime
- compiles your app into a series of *.be.sh files,
- but without including the BeSharp Runtime *.be.sh file,
- the client workstation is required to have the BeSharp Runtime installed,
- code is loaded dynamically, "when needed" by the app runtime.
Note: You can choose the default preset in the default.preset
file.
Your app source code is in the /app/src/
folder.
Target distribution code can be found in the /app/dist/<preset>/
folders.
There are two modes available for execution:
develop
- provides a better experience for development:- Bash shows errors in the direct source files,
- more code verifications occur during code runtime,
- but it works slower.
production
- executes final production code:- works faster due to compiler code optimizations being applied,
- some code verifications are off,
- but Bash shows errors in the target distribution files, making debugging harder.
The BeSharp framework provides three basic commands:
run
- compiles source code and executes the produced distribution code in production mode,
develop
- compiles source code and executes it in development mode,
build
- compiles source code into distribution code for all presets without executing it.
Please open BeSharp terminal:
- on Linux / MacOs - please run
./besharp
from the project folder, - on Windows - please run
besharp.bat
from the project folder.
To run app in a production mode:
run [ your app params ... ]
# to run given preset
run --preset <preset> [ your app params ... ]
# to force compilation of all files, instead of compiling only changed files
run --compile-all [ your app params ... ]
To run app in a developer mode:
develop [ params ... ]
# to run given preset
develop --preset <preset> [ your app params ... ]
# to force compilation of all files, instead of compiling only changed files
develop --compile-all [ your app params ... ]
To build the project into dist/
folders, without running it:
build
# to build given preset
build --preset <preset>
# to force compilation of all files, instead of compiling only changed files
build --compile-all
To run the project on the native Bash on Linux/Unix host machine, instead of BeSharp docker terminal:
./besharp build --preset <preset> \
&& ./app/dist/<preset>/app [ your app params ... ]
BeSharp Framework has been developed and tested on Ubuntu 20.04. If you have no Docker installed, you might want to try to execute BeSharp Framework directly on the host machine. See below examples:
./bin/run [ your app params ... ]
./bin/develop [ your app params ... ]
./bin/build --compile-all
Ideas:
- documentation and tutorials,
- working on performance,
- writing automated tests ( unit tests, performance tests, ... ),
- more syntax sugar ( e.x. to decrease amount of
@let
instructions in the code ) - testing and adapting architecture to various business scenarios,
- more code validations and better communication with the user,
- more language features: constants, enums, namespaces, public/private/protected visibility modifiers, "@final" keyword, etc.
- better debugger & development tooling,
- working on bigger OOP framework code base ( e.x. utils for strings, dates & float numbers, enhance collections, support for parallel programming, etc. )
- providing a packaging system allowing to publish and share code easily between developers,
- support for older Bash 4.3- versions,
- considering Bash 3+ support,
- more ...
BeSharp by Maciej Ostrowski (c) 2023