Lowkey
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.
Usage
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
Proxies provide a "flat" abstraction over the Abstract Syntax Tree.
Proxy Types:
-
FileProxy- The file path, its definitions and dependencies -
ClassProxy- The class and its methods -
MethodProxy- A method and its parameters -
ParamProxy- A parameter and its type -
ReturnProxy- The return type of the method
Method access
Lowkey['my_class.rb']['MyNamespace::MyClass'][:my_method] # => MethodProxyℹ️ For more information on proxies see the Proxy API.
Queries
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.
Mutations
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.
Proxy Mutations
Replacing a method's source code:
Lowkey['my_class.rb']['MyNamespace::MyClass'][:my_method].source_code = my_string_of_codeQuery Mutations [UNRELEASED]
Using the query keypath we can manipulate the AST:
Lowkey['my_class.rb::MyNamespace::MyClass.my_method'] = my_nodeExports
Proxy Exports
Export 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.Query Exports [UNRELEASED]
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.
Config
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
endArchitecture
sequenceDiagram
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
API
Lowkey
Methods:
-
load(file_path)- Use an absolute file path for best results. Relative paths are generated automatically in addition. ReturnsFileProxy
Proxy
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
MethodProxy
Properties:
-
params- Array ofParamProxys -
body- The method's bodyBodyProxy -
return_proxy- The method's return typeReturnProxy
Installation
Add gem 'lowkey' to your Gemfile then:
bundle install