{"id":13879706,"url":"https://github.com/digital-fabric/modulation","last_synced_at":"2025-04-13T00:45:09.890Z","repository":{"id":37270743,"uuid":"140055962","full_name":"digital-fabric/modulation","owner":"digital-fabric","description":"Modulation - explicit dependency management for Ruby","archived":false,"fork":false,"pushed_at":"2023-05-08T09:37:23.000Z","size":227,"stargazers_count":354,"open_issues_count":0,"forks_count":3,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-04-13T00:45:02.733Z","etag":null,"topics":["dependency-injection","inversion-of-control","module","namespace","package-management","ruby"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/digital-fabric.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null},"funding":{"github":"noteflakes"}},"created_at":"2018-07-07T05:39:35.000Z","updated_at":"2025-04-08T21:41:54.000Z","dependencies_parsed_at":"2024-01-13T21:11:01.395Z","dependency_job_id":null,"html_url":"https://github.com/digital-fabric/modulation","commit_stats":{"total_commits":160,"total_committers":2,"mean_commits":80.0,"dds":0.0625,"last_synced_commit":"6890622ba75aca586dc1bab7c9d52cbba950f91a"},"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/digital-fabric%2Fmodulation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/digital-fabric%2Fmodulation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/digital-fabric%2Fmodulation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/digital-fabric%2Fmodulation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/digital-fabric","download_url":"https://codeload.github.com/digital-fabric/modulation/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248650420,"owners_count":21139672,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dependency-injection","inversion-of-control","module","namespace","package-management","ruby"],"created_at":"2024-08-06T08:02:29.776Z","updated_at":"2025-04-13T00:45:09.858Z","avatar_url":"https://github.com/digital-fabric.png","language":"Ruby","readme":"# Modulation - Explicit Dependency Management for Ruby\n\n[![Gem Version](https://badge.fury.io/rb/modulation.svg)](http://rubygems.org/gems/modulation)\n[![Modulation Test](https://github.com/digital-fabric/modulation/workflows/Tests/badge.svg)](https://github.com/digital-fabric/modulation/actions?query=workflow%3ATests)\n[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/digital-fabric/modulation/blob/master/LICENSE)\n\n\u003e Modulation | mɒdjʊˈleɪʃ(ə)n | *Music* -  a change from one key to another in a\n\u003e piece of music.\n\n[INSTALL](#installing-modulation) |\n[GUIDE](#organizing-your-code-with-modulation) |\n[API](#api-reference) |\n[EXAMPLES](examples) |\n[RDOC](https://www.rubydoc.info/gems/modulation/)\n\n\nModulation provides an alternative way of organizing your Ruby code. Modulation\nlets you explicitly import and export declarations in order to better control\ndependencies in your codebase. Modulation helps you refrain from littering\nthe global namespace with a myriad modules, or complex multi-level nested\nmodule hierarchies.\n\nUsing Modulation, you will always be able to tell where a class or module comes\nfrom, and you'll have full control over which parts of a module's code you wish\nto expose to the outside world. Modulation can also help you write Ruby code in\na functional style, minimizing boilerplate code.\n\n\u003e Note: Modulation is not a replacement for RubyGems. Rather, Modulation is\n\u003e intended for managing dependencies between source files *inside* your Ruby\n\u003e applications. Though it does support loading gems that were written using\n\u003e Modulation, it is not intended as a comprehensive solution for using\n\u003e third-party libraries.\n\n## Features\n\n- **[Complete isolation of each module](#organizing-your-code-with-modulation)**\n  prevents literring of the global namespace.\n- **Explicit [exporting](#exporting-declarations) and\n  [importing](#importing-declarations) of methods and constants** lets you\n  control the public interface for each module, as well as keep track of all\n  dependencies in your code.\n- **[Tagged paths](#using-tags-to-designate-common-subdirectories)** simplify\n  the management of dependencies in large applications.\n- **[Lazy Loading](#lazy-loading)** improves start up time and memory\n  consumption.\n- **[Hot module reloading](#reloading-modules)** streamlines your development\n  process.\n- **[Module mocking](#mocking-modules)** facilitates testing.\n- **[Dependency introspection](#dependency-introspection)** lets you introspect\n  your dependencies at runtime.\n- **[Application packing](#packing-applications-with-modulation)** lets you\n  bundle your code in a single, optionally obfuscated file (WIP).\n\n## Rationale\n\nYou're probably asking yourself \"what the ****?\" , but when your Ruby app grows\nand is split into multiple files loaded using `#require`, you'll soon hit some\nissues:\n\n- Once a file is `#require`d, any class, module or constant in it is available\n  to any other file in your codebase. All \"globals\" (classes, modules,\n  constants) are loaded, well, globally, in a single namespace. Name conflicts\n  are easy in Ruby.\n- To avoid class name conflicts, classes need to be nested under a single\n  hierarchical tree, sometime reaching 4 levels or more. Just look at Rails.\n- Since a `#require`d class or module can be loaded in any file and then made\n  available to all files, it's easy to lose track of where it was loaded, and\n  where it is used.\n- There's no easy way to hide implementation-specific classes or methods. Yes,\n  there's `#private`, `#private_constant` etc, but by default everything is\n  `#public`!\n- Extracting functionality is harder when modules are namespaced and\n  dependencies are implicit.\n- Writing reusable functional code requires wrapping it in modules using\n  `class \u003c\u003c self`, `def self.foo ...`, `extend self` or `include Singleton`\n  (the pain of implementing singletons in Ruby has been\n  [discussed](https://practicingruby.com/articles/ruby-and-the-singleton-pattern-dont-get-along)\n  [before](https://ieftimov.com/singleton-pattern).)\n\n\u003e There's a [recent discussion](https://bugs.ruby-lang.org/issues/14982) on the\n\u003e Ruby bug tracker regarding possible solutions to the problem of top-level\n\u003e name collision. Hopefully, the present gem could contribute to an eventual\n\u003e \"official\" API.\n\nPersonally, I have found that managing dependencies with `#require` in large\ncodebases is... not as elegant or painfree as I would expect from a\nfirst-class development environment. I also wanted to have a better solution\nfor writing in a functional style.\n\nSo I came up with Modulation, a small gem that takes a different approach to\norganizing Ruby code: any so-called global declarations are hidden unless\nexplicitly exported, and the global namespace remains clutter-free. All\ndependencies between source files are explicit, visible, and easy to understand.\n\n## Installing Modulation\n\nYou can install the Modulation using `gem install`, or add it to your `Gemfile`:\n\n```ruby\ngem 'modulation'\n```\n\n## Organizing your code with Modulation\n\nModulation builds on the idea of a Ruby `Module` as a\n[\"collection of methods and constants\"](https://ruby-doc.org/core-2.6.5/Module.html).\nUsing modulation, each Ruby source file becomes a module. Modules usually\nexport method and constant declarations (usually an API for a specific,\nwell-defined functionality) to be shared with other modules. Modules can also\nimport declarations from other modules. Anything not exported remains hidden\ninside the module and normally cannot be accessed from the outside.\n\nEach source file is evaluated in the context of a newly-created `Module`\ninstance, with some additional methods for introspection and miscellaneous\noperations such as [hot reloading](#reloading-modules).\n\nModulation provides alternative APIs for loading modules. Instead of using\n`require` and `require_relative`, we use `import`, `import_map` etc, discussed\nin detail in the [API reference](#api-reference).\n\n## Basic Usage\n\n### Exporting declarations\n\nAny class, module or constant be exported using `#export`:\n\n```ruby\nexport :User, :Session\n\nclass User\n...\nend\n\nclass Session\n...\nend\n```\n\nA module may also expose a set of methods without using `class \u003c\u003c self`, for\nexample when writing in a functional style:\n\n*seq.rb*\n```ruby\nexport :fib, :luc\n\ndef fib(n)\n  (0..1).include?(n) ? n : (fib(n - 1) + fib(n - 2))\nend\n\ndef luc(n)\n  (0..1).include?(n) ? (2 - n) : (luc(n - 1) + luc(n - 2))\nend\n```\n*app.rb*\n```ruby\nrequire 'modulation'\nSeq = import('./seq')\nputs Seq.fib(10)\n```\n\nAnother way to export methods and constants is by passing a hash to `#export`:\n\n*module.rb*\n```ruby\nexport(\n  foo: :bar,\n  baz: -\u003e { 'hello' },\n  MY_CONST: 42\n)\n\ndef bar\n  :baz\nend\n```\n\n*app.rb*\n```ruby\nm = import('./module')\nm.foo #=\u003e :baz\nm.baz #=\u003e 'hello'\nm::MY_CONST #=\u003e 42\n```\n\nAny capitalized key will be interpreted as a const, otherwise it will be defined\nas a method. If the value is a symbol, Modulation will look for the\ncorresponding method or const definition and will treat the key as an alias.\n\nThe `export` method can be called multiple times. Its behavior is additive:\n\n```ruby\n# this:\nexport :foo, :bar\n\n# is the same as this:\nexport :foo\nexport :bar\n```\n\n### Importing declarations\n\nDeclarations from another module can be imported using `#import`:\n\n```ruby\nrequire 'modulation'\nModels = import('./models')\n...\n\nuser = Models::User.new(...)\n\n...\n```\n\nAlternatively, a module interested in a single declaration from another module\ncan use the following technique:\n\n```ruby\nrequire 'modulation'\nUser = import('./models')::User\n...\n\nuser = User.new(...)\n```\n\n### A word about paths\n\nPaths given to `import` are always considered relative to the importing file,\nunless they are absolute (e.g. `/home/dave/repo/my_app`), specify a\n[tag](#using-tags-to-designate-common-subdirectories) or reference a\n[gem](#importing-gems-using-modulation). This is true for all Modulation APIs\nthat accept path arguments.\n\n## Advanced Usage\n\n### Using tags to designate common subdirectories\n\nNormally, module paths are always relative to the file calling the `#import`\nmethod, just like `#require_relative`. This can become a problem once you start\nmoving your source files around. In addition, in applications where your source\nfiles are arranged in multiple directories, it can quickly become tedious to do\nstuff like `Post = import('../models/post')`.\n\nModulation provides an alternative to relative paths in the form of tagged\nsources. A tagged source is simply a path associated with a label. For example,\nan application may tag `lib/models` simply as `@models`. Once tags are defined,\nthey can be used when importing files, e.g. `import('@models/post')`.\n\nTo define tags, use `Modulation.add_tags`:\n\n```ruby\nModulation.add_tags(\n  models: '../lib/models',\n  views:  '../lib/views'\n)\n\n...\n\nUser = import '@models/user'\n```\n\n### Importing all source files in a directory\n\nTo load all source files in a directory you can use `#import_all`:\n\n```ruby\nimport_all('./ext')\n```\n\nGroups of modules providing a uniform interface can also be loaded using\n`#import_map`:\n\n```ruby\nAPI = import_map('./math_api') #=\u003e hash mapping filenames to modules\nAPI.keys #=\u003e ['add', 'mul', 'sub', 'div']\nAPI['add'].(2, 2) #=\u003e 4\n```\n\nThe `#import_map` takes an optional block to transform hash keys:\n\n```ruby\nAPI = import_map('./math_api') { |name, mod| name.to_sym }\nAPI.keys #=\u003e [:add, :mul, :sub, :div]\nAPI[:add].(2, 2) #=\u003e 4\n```\n\n### Importing methods into classes and objects\n\nModulation provides the `#extend_from` and `#include_from` methods to include\nimported methods in classes and objects:\n\n```ruby\nmodule Sequences\n  extend_from('./seq.rb')\nend\n\nSequences.fib(5)\n\n# extend integers\nrequire 'modulation'\nclass Integer\n  include_from('./seq.rb')\n\n  def seq(kind)\n    send(kind, self)\n  end\nend\n\n5.seq(:fib)\n```\n\nThe `#include_from` method accepts an optional list of symbols to import:\n\n```ruby\nclass Integer\n  include_from './seq.rb', :fib\nend\n\n5.fib\n```\n\n### Default exports\n\nA module may wish to expose just a single class or constant, in which case it\ncan use `#export_default`:\n\n*user.rb*\n```ruby\nexport_default :User\n\nclass User\n  ...\nend\n```\n\n*app.rb*\n```ruby\nrequire 'modulation'\nUser = import('./user')\nUser.new(...)\n```\n\nThe default exported value can also be defined directly thus:\n\n*config.rb*\n```ruby\nexport_default(\n  host: 'localhost',\n  port: 1234,\n  ...\n)\n```\n\n*app.rb*\n```ruby\nrequire 'modulation'\nconfig = import('./config')\ndb.connect(config[:host], config[:port])\n```\n\n### Circular dependencies\n\nCircular dependencies, while not the best practice for organizing a code base,\nare sometimes useful. Modulation supports circular dependencies, with the\nexception of modules with default exports.\n\n### Accessing a module's root namespace from nested modules within itself\n\nThe special constant `MODULE` allows you to access the containing module from\nnested modules or classes. This lets you call methods defined in the module's\nroot namespace, or otherwise introspect the module:\n\n```ruby\nexport :AsyncServer\n\n# Await a promise-like callable\ndef await\n  calling_fiber = Fiber.current\n  p = -\u003e(v = nil) {calling_fiber.resume v}\n  yield p\n  Fiber.yield\nend\n\nclass AsyncServer \u003c SomeTCPServer\n  def async_read\n    MODULE.await {|p| on_read {|data| p.(data)}}\n  end\nend\n```\n\n### Accessing the global namespace\n\nIf you need to access the global namespace inside a module just prefix the\nclass name with double colons:\n\n```ruby\nclass ::GlobalClass\n  ...\nend\n\n::ENV = { ... }\n\nwhat_is = ::THE_MEANING_OF_LIFE\n```\n\n### Programmatic module creation\n\nIn addition to loading modules from files, modules can be created dynamically at\nruntime using `Modulation.create`. You can create modules by supplying a hash\nprototype, a string or a block:\n\n```ruby\n# Using a hash prototype\nm = Modulation.create(\n  add: -\u003e x, y { x + y },\n  mul: -\u003e x, y { x * y }\n)\nm.add(2, 3)\nm.mul(2, 3)\n\n# Using a string\nm = Modulation.create \u003c\u003c~RUBY\nexport :foo\n\ndef foo\n  :bar\nend\nRUBY\n\nm.foo\n\n# Using a block\nm = Modulation.create do { |mod|\n  export :foo\n\n  def foo\n    :bar\n  end\n\n  class mod::BAZ\n    ...\n  end\n}\n\nm.foo\n```\n\nThe creation of a objects using a hash prototype is also available as a separate\ngem called [eg](https://github.com/digital-fabric/eg/).\n\n### Unit testing modules\n\nMethods and constants that are not exported can be tested using the `#__expose!`\nmethod. Thus you can keep implementation details hidden, while being able to\neasily test them:\n\n*parser.rb*\n```ruby\nexport :parse\n\ndef parse(inp)\n  split(inp).map(\u0026:to_sym)\nend\n\n# private method\ndef split(inp)\n  inp.split(',').map(\u0026:strip)\nend\n```\n\n*test_seq.rb*\n```ruby\nrequire 'modulation'\nrequire 'minitest/autorun'\n\nParser = import('../lib/parser').__expose!\n\nclass FibTest \u003c Minitest::Test\n  def test_that_split_trims_split_parts\n    assert_equal(%w[abc def ghi], Parser.split(' abc ,def , ghi  '))\n  end\nend\n```\n\n### Mocking modules\n\nModules loaded by Modulation can be easily mocked when running tests or specs,\nusing `Modulation.mock`:\n\n```ruby\nrequire 'minitest/autorun'\nrequire 'modulation'\n\nmodule MockStorage\n  extend self\n\n  def get_user(user_id)\n    {\n      user_id: user_id,\n      name: 'John Doe',\n      email: 'johndoe@gmail.com'\n    }\n  end\nend\n\nclass UserControllerTest \u003c Minitest::Test\n  def test_user_storage\n    Modulation.mock('../lib/storage', MockStorage) do\n      controller = UserController.new\n      ...\n    end\n  end\nend\n```\n\n`Modulation.mock` accepts a module path and a receiver, and the module stays\nmocked within the given block.\n\n### Lazy Loading\n\nModulation allows the use of lazy-loaded modules - loading of modules only once\nthey're needed by the application, in similar fashion to `Module#auto_load`. To\nlazy load modules use the `#auto_import` method, which takes a constant name and\na path:\n\n```ruby\nexport :foo\n\nauto_import :BAR, './bar'\n\ndef foo\n  # the bar module will only be loaded once this method is called\n  MODULE::BAR\nend\n```\n\n\u003e Lazy-loaded constants must always be qualified. When referring to a\n\u003e lazy-loaded constant from the module's top namespace, use the `MODULE`\n\u003e namespace, as shown above.\n\nThe `#auto_import` method can also take a hash mapping constant names to paths.\nThis is especially useful when multiple concerns are grouped under a single\nnamespace:\n\n```ruby\nexport_default :SuperNet\n\nmodule SuperNet\n  auto_import(\n    HTTP1:      './http1',\n    HTTP2:      './http2',\n    WebSockets: './websockets'\n  )\nend\n\nSuperNet::HTTP1 #=\u003e loads the http1 module\n```\n\n### Reloading modules\n\nModules can be reloaded at run-time for easy hot code reloading:\n\n```ruby\nrequire 'modulation'\nSQL = import('./sql')\n...\nSQL.__reload!\n```\n\nAnother way to reload modules is using `Modulation.reload`, which accepts a\nmodule or a filename:\n\n```ruby\nrequire 'filewatcher'\n\nFileWatcher.new(['lib']).watch do |fn, event|\n  if(event == :changed)\n    Modulation.reload(fn)\n  end\nend\n```\n\n\u003e When a module is reloaded, its entire content - constants and methods - will\n\u003e be replaced. That means that any code using that module could continue to use\n\u003e it without even being aware it was reloaded, providing its API has not\n\u003e changed.\n\nReloading of modules with default exports is also possible. Modulation will\nextend the exported value with a `#__reload!` method. The value will need to be\nreassigned:\n\n```ruby\nrequire 'modulation'\nsettings = import('settings')\n...\nsettings = settings.__reload!\n```\n\nPlease note that Modulation does not include a directory watcher that\nautomatically reloads changed modules. This is due to multiple considerations\nthat include the chosen threading model, or the reactor engine in use, or even\nthe chosen solution for watching files (whether it's an external gem or an\ninternal tool).\n\nIt is, however, quite trivial to watch files using\n[`directory_watcher`](https://rubygems.org/gems/directory_watcher/):\n\n```ruby\nrequire 'directory_watcher'\n\ndw = DirectoryWatcher.new 'lib', glob: '**/*.rb', interval: 2, pre_load: true\ndw.add_observer do |*events|\n  events.each do |e|\n    next unless e.type == :modified\n\n    Modulation.reload e.path\n  end\nend\n\ndw.start\n```\n\n### Retaining state between reloads\n\nBefore a module is reloaded, all of its methods and constants are removed. In\nsome cases, a module might need to retain state across reloads. You can do this\nby simply using instance variables:\n\n```ruby\nexport :value, :inc\n\n@counter ||= 0\n\ndef value\n  @counter\nend\n\ndef incr\n  @counter += 1\nend\n```\n\nCare must be taken not to reassign values outside of methods, as this will\noverwrite any value retained in the instance variable. To assign initial values,\nuse the `||=` operator as in the example above. See also the\n[reload example](examples/reload).\n\n### Dependency introspection\n\nModulation allows runtime introspection of dependencies between modules. You can\ninterrogate a module's dependencies (i.e. the modules it imports) by calling\n`#__depedencies`:\n\n*m1.rb*\n```ruby\nimport ('./m2')\n```\n\n*app.rb*\n```ruby\nm1 = import('./m1')\nm1.__depedencies #=\u003e [\u003cModule m2\u003e]\n```\n\nYou can also iterate over a module's entire dependency tree by using\n`#__traverse_dependencies`:\n\n```ruby\nm1 = import('./m1')\nm1.__traverse_dependencies { |mod| ... }\n```\n\nTo introspect reverse dependencies (modules *using* a particular module), use\n`#__dependent_modules`:\n\n```ruby\nm1 = import('./m1')\nm1.__depedencies #=\u003e [\u003cModule m2\u003e]\nm1.__dependencies.first.__dependent_modules #=\u003e [\u003cModule m1\u003e]\n```\n\n## Running Modulation-based applications\n\nModulation provides a binary script for running Modulation-based applications.\n`mdl` is a wrapper around Ruby that loads your application's main file as a\nmodule, and then runs your application's entry point method. Let's look at a\nsample application:\n\n*app.rb*\n```ruby\ndef greet(name)\n  puts \"Hello, #{name}!\"\nend\n\ndef main\n  print \"Enter your name: \"\n  name = gets\n  greet(name)\nend\n```\n\nTo run this application, execute `mdl app.rb`, or `mdl run app.rb`. `mdl` will\nautomatically require the `modulation` gem and call the application's entry\npoint, `#main`.\n\n## Packing applications with Modulation\n\n\u003e *Note*: application packing is at the present time an experimental feature.\n\u003e There might be security concerns for packaging your app, such as leaking\n\u003e filenames from the developer's machine.\n\nModulation can also be used to package your entire application into a single\nportable file that can be copied to another machine and run as is. To package\nyour app, use `mdl pack`. This command will perform a dynamic analysis of all\nthe app's dependencies and will put them together into a single Ruby file.\n\nFor more information have a look at the [app](examples/app) example.\n\n## Writing gems using Modulation\n\nModulation can be used to write gems, providing fine-grained control over your\ngem's public APIs and letting you hide any implementation details. In order to\nallow loading a gem using either `#require` or `#import`, code your gem's main\nfile normally, but add  `require 'modulation/gem'` at the top, and export your\ngem's main namespace as a default export, e.g.:\n\n```ruby\nrequire 'modulation/gem'\n\nexport_default :MyGem\n\nmodule MyGem\n  ...\n  MyClass = import('my_gem/my_class')\n  ...\nend\n```\n\n## Importing gems using Modulation\n\nGems written using modulation can also be loaded using `#import`. If modulation\ndoes not find the module specified by the given relative path, it will attempt\nto load a gem by the same name. It is also possible to load specific files\ninside modules by specifying a sub-path:\n\n```ruby\nrequire 'modulation'\nMyFeature = import 'my_gem/my_feature'\n```\n\n\u003e **Note**: Since there's not much of a point in `#import`ing gems that do not\n\u003e use Modulation to export symbols, Modulation will refuse to import any gem\n\u003e that does not depend on Modulation.\n\n## Writing modules that patch external classes or modules\n\nIt is generally recommended you refrain from causing side effects or patching\nexternal code in your modules. When you do have to patch external classes or\nmodules (i.e. core, stdlib, or some third-party code) in your module, it's\nuseful to remember that any module may be eventually reloaded by the application\ncode. This means that any patching done during the loading of your module must\nbe [idempotent](https://en.wikipedia.org/wiki/Idempotence), i.e. have the same\neffect when performed multiple times. Take for example the following module\ncode:\n\n```ruby\nmodule ::Kernel\n  # aliasing #sleep more than once will break your code\n  alias_method :orig_sleep, :sleep\n\n  def sleep(duration)\n    STDERR.puts \"Going to sleep...\"\n    orig_sleep(duration)\n    STDERR.puts \"Woke up!\"\n  end\nend\n```\n\nRunning the above code more than once would cause an infinite loop when calling\n`Kernel#sleep`. In order to prevent this situation, modulation provides the\n`Module#alias_method_once` method, which prevents aliasing the original method\nmore than once:\n\n```ruby\nmodule ::Kernel\n  # alias_method_once is idempotent\n  alias_method_once :orig_sleep, :sleep\n\n  def sleep(duration)\n    STDERR.puts \"Going to sleep...\"\n    orig_sleep(duration)\n    STDERR.puts \"Woke up!\"\n  end\nend\n```\n\n## Coding style recommendations\n\n* Import modules into constants, not variables:\n\n  ```ruby\n  Settings = import('./settings')\n  ```\n\n* Place your exports at the top of your module, followed by `#require`s,\n  followed by `#import`s:\n\n  ```ruby\n  export :foo, :bar, :baz\n\n  require 'json'\n\n  Core = import('./core')\n\n  ...\n  ```\n\n## API Reference\n\n### Kernel\n\n#### `Kernel#auto_import_map(path, options = {})`\n\nReturns a hash mapping keys to corresponding module files inside the given\ndirectory path. Modules are loaded automatically upon accessing hash keys.\n\n#### `Kernel#import(path)`\n\nReturns a loaded module identified by the given path. The path can contain\n[tags](#using-tags-to-designate-common-subdirectories)\n\n#### `Kernel#import_all(path)`\n\n#### `Kernel#import_map(path, options = {})`\n\n### Module\n\n#### `Module#__module_info`\n\nReturns a hash containing information about the module. This currently includes\nthe following entries:\n\nlocation|Absolute module file path\nexported_symbols|Array containing all symbols exported by the module\n\n### `Module#__reload!`\n\n#### `Module#alias_method_once(new_name, old_name)`\n\n#### `Module#auto_import(sym, path)`\n\n#### `Module#export(*symbols)`\n\n#### `Module#export_default(value)`\n\n#### `Module#export_from_receiver(receiver)`\n\n#### `Module#extend_from(path)`\n\n#### `Module#include_from(path, *symbols)`\n\n#### `Module::MODULE`\n\n### Modulation\n\n#### `Modulation.full_backtrace!`\n\n#### `Modulation.reload`\n\n## Why you should not use Modulation\n\n- Modulation is not production-ready.\n- Modulation is not thread-safe.\n- Modulation doesn't play well with rdoc/yard.\n- Modulation (probably) doesn't play well with `Marshal`.\n- Modulation (probably) doesn't play well with code-analysis tools.\n","funding_links":["https://github.com/sponsors/noteflakes"],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdigital-fabric%2Fmodulation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdigital-fabric%2Fmodulation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdigital-fabric%2Fmodulation/lists"}