Prism is amazing and opens up new ways to access metadata in Ruby. However:
- Loading the Abstract Syntax Tree (AST) multiple times is inefficient
- We need higher level abstractions such as classes and methods
- Navigating the AST can be difficult
Lowkey provides a central API for storing this metadata once and accessing it multiple times. It's the secret sauce 🌶️ behind LowType, LowLoad and Raindeer in general.
Load a file:
# Absolute path or path relative to the starting Ruby process.
Lowkey.load('my_class.rb')Access the resulting proxies:
Lowkey['my_class.rb'] # => FileProxy
Lowkey['my_class.rb']['MyNamespace::MyClass'] # => ClassProxyProxies provide a "flat" abstraction over the Abstract Syntax Tree.
Proxy Types:
FileProxy- The file path, its definitions and dependenciesClassProxy- The class and its methodsMethodProxy- A method and its parametersParamProxy- A parameter and its typeReturnProxy- The return type of the method
Lowkey['my_class.rb']['MyNamespace::MyClass'][:my_method] # => MethodProxyℹ️ For more information on proxies see the Proxy API.
Queries access nested nodes within the AST via keypath syntax:
Lowkey['my_class.rb::MyNamespace::MyClass.my_method'] # => MethodDefNodeA query can start at any proxy and still use the keypath syntax:
# Query from a file proxy.
file_proxy = Lowkey['my_class.rb']
file_proxy['MyNamespace::MyClass.my_method'] # => MethodDefNode
# Query from a class proxy.
class_proxy = Lowkey['my_class.rb']['MyNamespace::MyClass']
class_proxy['.my_method'] # => MethodDefNodeℹ️ Query keypaths contain dots "." and return nodes. They target the name attribute of nodes.
You can mutate a file using either Proxies or Queries:
| Difficulty | Structure | Mutations | |
|---|---|---|---|
| Proxies | Easy | Flat | Preserves existing code and line numbers when possible |
| Queries | Medium | Nested | Generates new code from the Abstract Syntax Tree |
ℹ️ Both approaches can be mixed together, for example; using queries to get data for a proxy.
Replacing a method's source code:
Lowkey['my_class.rb']['MyNamespace::MyClass'][:my_method].source_code = my_string_of_codeUsing the query keypath we can manipulate the AST:
Lowkey['my_class.rb::MyNamespace::MyClass.my_method'] = my_nodeExport the source code for mutated proxies to memory:
Lowkey['my_class.rb'].export
Lowkey['my_class.rb']['MyNamespace::MyClass'].export
Lowkey['my_class.rb']['MyNamespace::MyClass'][:my_method].exportSave the source code for mutated proxies to disk: [UNRELEASED]
Lowkey['my_class.rb'].save(file_path:) # Replaces entire file.
Lowkey['my_class.rb']['MyNamespace::MyClass'].save(file_path:) # Replaces part of a file.
Lowkey['my_class.rb']['MyNamespace::MyClass'][:my_method].save(file_path:) # Replaces part of a file.Export generated code for mutated nodes to memory: [UNRELEASED]
Lowkey['my_class.rb.root_node'].export # Special selector for the top level node.
Lowkey['my_class.rb::MyNamespace::MyClass.root_node'].export
Lowkey['my_class.rb::MyNamespace::MyClass.my_method'].exportSave generated code for mutated nodes to disk: [UNRELEASED]
Lowkey['my_class.rb.root_node'].save(file_path:) # Replaces entire file.
Lowkey['my_class.rb::MyNamespace::MyClass'].save(file_path:) # Replaces entire file.
Lowkey['my_class.rb::MyNamespace::MyClass.my_method'].save(file_path:) # Replaces entire file.ℹ️ Code is generated from the AST and will not match the source code that was originally loaded.
Copy and paste the following and change the defaults to configure Lowkey:
# This configuration should be set before calling "Lowkey.load".
Lowkey.configure do |config|
# A big benefit of Lowkey is its caching of abstract syntax trees, file proxies and class proxies.
# But to save memory you should clear them after the "class load"/setup stage of your application.
# Set to "false" or call "Lowkey.clear" after you no longer need Lowkey, such as in a boot script.
config.cache = true
endsequenceDiagram
participant Lowkey@{ "type" : "database" }
participant FileProxy
participant ClassProxy@{ "type" : "collections" }
participant Proxies@{ "type" : "collections" }
participant Sources@{ "type" : "collections" }
Lowkey->>FileProxy: Parses AST
FileProxy->>ClassProxy: Manages
ClassProxy->>Proxies: Manages
Sources->>Proxies: Per proxy
Sources--)FileProxy: Mutates source code
Lowkey->>Lowkey: Stores proxies
Methods:
load(file_path)- Use an absolute file path for best results. Relative paths are generated automatically in addition. ReturnsFileProxy
Methods:
wrap(prefix:, suffix:)- Wrap the code inside the prefix and suffix. Handles indentation and line breaks.export- Export the modified source code to string
Properties:
params- Array ofParamProxysbody- The method's bodyBodyProxyreturn_proxy- The method's return typeReturnProxy
Add gem 'lowkey' to your Gemfile then:
bundle install