You [Gerald Bauer¹] have been permanently banned [for life] from participating in r/ruby (because of your writing off / outside of r/ruby). I do not see your participation adding anything to this [ruby] community.
-- Richard Schneeman (r/ruby mod and fanatic illiberal ultra leftie on a cancel culture mission)
¹: I know. Who cares? Who is this Gerald Bauer anyway. A random nobody for sure. It just happens that I am the admin among other things of Planet Ruby.
Case Studies of Code of Conduct "Cancel Culture" Out-Of-Control Power Abuse - Ruby - A Call for Tolerance On Ruby-Talk Results In Ban On Reddit RubyUpdate (August, 2022) - A Call for More Tolerance And Call For No-Ban Policy Results In Ban On Ruby-Talk (With No Reason Given)
> I just banned gerald.bauer@gmail.com. > > -- SHIBATA Hiroshi > >> THANK YOU >> >> -- Ryan Davis >> >> >> My full support to moderators. >> >> -- Xavier Noria >> >> My full support to moderators. >> >> -- Carlo E. Prelz >> >> That's fun. >> >> -- Alice
« Ruby Glimmer Days 2021, January 26th to January 29th - 4 Days of Ruby (Desktop) Gems
Written by Andy Maleh
Software Engineering Expert from Montreal, Quebec. Creator of Glimmer and Abstract Feature Branch. Speaker at RailsConf, RubyConf, AgileConf, EclipseCon, EclipseWorld. Master in Software Engineering, DePaul University, Chicago. Blogs at Code Mastery Takes Commitment To Bold Coding Adventures. Snowboarder and Drummer.
Glimmer started out as a domain-specific language (DSL) for the Standard Widget Toolkit (SWT) and grew into a domain-specific language (DSL) engine supporting scripting multiple graphical user interfaces. Glimmer’s namesake is referring to the glimmer of ruby in graphical user interfaces.
Glimmer consists of:
Glimmer offers:
instance_exec
/eval
.Glimmer
module, so the rest of the code is fully safe from namespace pollution.Glimmer currently supports:
Glimmer is fundamentally a domain-specific language engine and syntax consists mainly of:
table
for a table widget)table(:multi)
for a multi-line selection table widget){ table_column { text 'Name'} }
as in table(:multi) { table_column { text 'Name'} }
for a multi-line selection table widget with a table column having header text property 'Name'
as content)The Glimmer engine allows mixing of languages, which comes in handy when doing things like rendering a desktop browser
widget additionally leveraging the HTML and CSS domain-specific languages for its content.
Domain-specific languages are activated by top-level keywords (expressions denoted as TopLevelExpression
). For example, the html
keyword activates the Glimmer for XML and the css
keyword activates the Glimmer for CSS. Glimmer automatically recognizes top-level keywords in each language and activates the it accordingly. Once done processing a nested top-level keyword, Glimmer switches back to the prior language automatically.
By default, all loaded domain-specific languages (required gems) are enabled.
For example, this shows “Hello, World!” inside a Glimmer for SWT desktop app browser
widget using html
and css
from Glimmer for XML and Glimmer for CSS:
require 'glimmer-dsl-swt'
require 'glimmer-dsl-xml'
require 'glimmer-dsl-css'
include Glimmer
shell {
minimum_size 130, 130
@browser = browser {
text html {
head {
meta(name: "viewport", content: "width=device-width, initial-scale=2.0")
style {
css {
h1 {
background 'yellow'
}
}
}
}
body {
h1 { "Hello, World!" }
}
}
}
}.open
The Glimmer engine’s architecture is based on the following design patterns and data structures:
Glimmer’s use of the Interpreter Design Pattern in processing language scripts is also known as the Virtual Machine Architectural Style. After all, expressions are virtual machine opcodes that process nested keywords stored in a stack. I built Glimmer’s original domain-specific language back in 2007 without knowing the Virtual Machine Architectural Style (except perhaps as an esoteric technology powering java), but stumbled upon it anyways through following the Gang of Four Design Patterns mentioned above, chiefly the Interpreter Design Pattern.
Every keyword in a Glimmer language is represented by an expression that is processed by an Expression
subclass selected from a chain of expressions (interpreters) pre-configured in a domain-specific language chain of responsibility via Glimmer::DSL::Engine.add_dynamic_expressions(DSLNameModule, expression_names_array)
.
Expressions are either:
StaticExpression
, which is a subclass of Expression
): if they represent a single pre-identified keyword (e.g. color
or display
)Expression
): if they represent keywords calculated on the fly during processing (e.g. an SWT widget like label
or a random XML element called folder
representing <folder></folder>
)Optionally, expressions can be parent expressions that contain other expressions, and must include the ParentExpression
mixin module as such.
Additionally, every expression that serves as a top-level entry point into the language must mixin TopLevelExpression
Static expressions are optimized in performance since they pre-define methods on the Glimmer
module matching the static keywords they represent (e.g. color
causes creating a Glimmer#color
method for processing color
expressions) and completely bypass as a result the Glimmer Engine Chain of Responsibility. That said, they must be avoided if the same keyword might occur multiple times, but with different requirements for arguments, block, and parenthood type.
Every Expression
sublcass must specify two methods at least:
can_interpret?(parent, keyword, *args, &block)
: to quickly test if the keyword and arg/block/parent combination qualifies for interpretation by the current Expression
or to otherwise delegate to the next expression in the chain of responsibility.interpret(parent, keyword, *args, &block)
: to go ahead and interpret a DSL expression that qualified for interpretationStaticExpression
sublcasses may skip the can_interpret?
method since they include a default implementation for it that matches the name of the keyword from the class name by convention. For example, a color
keyword would have a ColorExpression
class, so color
is inferred automatically from class name and used in deciding whether the class can handle a color
keyword or not.
ParentExpression
subclasses can optionally override this extra method, which is included by default and simply invokes the parent’s passed block to process its children:
add_content(parent, &block)
For example, some parent widgets use their block for other reasons or process their children at very specific times, so they may override that method and disable it, or otherwise call super
and do additional work.
Example of a dynamic expression:
module Glimmer
module DSL
module SWT
class WidgetExpression < Expression
include ParentExpression
EXCLUDED_KEYWORDS = %w[shell display tab_item]
def can_interpret?(parent, keyword, *args, &block)
!EXCLUDED_KEYWORDS.include?(keyword) and
parent.respond_to?(:swt_widget) and
Glimmer::SWT::WidgetProxy.widget_exists?(keyword)
end
def interpret(parent, keyword, *args, &block)
Glimmer::SWT::WidgetProxy.create(keyword, parent, args)
end
def add_content(parent, &block)
super
parent.post_add_content
end
end
end
end
end
Example of a static expression (does not need can_interpret?
):
module Glimmer
module DSL
module Opal
class ColorExpression < StaticExpression
include TopLevelExpression
def interpret(parent, keyword, *args, &block)
Glimmer::SWT::ColorProxy.new(*args)
end
end
end
end
end
Epressions go into the glimmer/dsl/{dsl_name}
namespace directory.
Also, every domain-specific language requires a glimmer/dsl/{dsl_name}/dsl.rb
file, which configures the language into Glimmer via a call to:
Glimmer::DSL::Engine.add_dynamic_expressions(DSLNameModule, expression_names_array)
Expression names are underscored verions of Expression
subclass names minus the _expression
suffix.
For example, here is an SWT configuration:
require 'glimmer/launcher'
require Glimmer::Launcher.swt_jar_file
require 'glimmer/dsl/engine'
Dir[File.expand_path('../*_expression.rb', __FILE__)].each {|f| require f}
module Glimmer
module DSL
module SWT
Engine.add_dynamic_expressions(
SWT,
%w[
layout
widget_listener
combo_selection_data_binding
checkbox_group_selection_data_binding
radio_group_selection_data_binding
list_selection_data_binding
tree_items_data_binding
table_items_data_binding
data_binding
cursor
font
image
property
block_property
widget
custom_widget
]
)
end
end
end
In summary, these are the files needed to author a Glimmer domain-specific language:
glimmer/dsl/[dsl_name]/dsl.rb
: requires and adds all dynamic expressions to [dsl_name] Glimmer languageglimmer/dsl/[dsl_name]/[expresion_name]_expresion.rb
: needed for every [expresion_name] expression, whether dynamic or staticData-Binding enables binding graphical user interface properties (like text and color) to model attributes (like name and age).
It relies on the Observer Design Pattern and Model-View-Presenter (MVP) Architectural Pattern - a variation on Model-View-Cotnroller (MVC).
These are the main classes concerning data-binding:
Observer
: Provides general observer support including unique registration and deregistration for cleanup and prevention of memory leaks. Main methods concerned are: call
, register
(alias: observe
), and unregister
(alias: unobserve
or deregister
)Observable
: General super-module for all observables. Main methods concerned are: add_observer
and remove_observer
ObservableModel
: Mixin module for any observable model with observable attributes. In addition to Observable
methods, it has a notify_observers
method to be called when changes occur. It automatically enhances all attribute setters (ending with =
) to notify observers on changes. Also, it automatically handles observing array attributes using ObservableArray
appropriately so they would notify observers upon array mutation changes.ObservableArray
: Mixin module for any observable array collection that automatically handles notifying observers upon performing array mutation operations (e.g. push
or delete
)ModelBinding
: a higher-level abstraction that relies on all the other observer/observable classes to support basic data-binding, nested data-binding, and computed data-binding
Built with Ruby
(running Jekyll)
on 2023-01-25 18:05:39 +0000 in 0.371 seconds.
Hosted on GitHub Pages.
</> Source on GitHub.
(0) Dedicated to the public domain.