{"id":16115233,"url":"https://github.com/pvande/dragonborn","last_synced_at":"2025-03-18T10:30:30.136Z","repository":{"id":233375660,"uuid":"787115952","full_name":"pvande/dragonborn","owner":"pvande","description":"Manage your cross-file dependencies and requirements in Rails-like comfort!","archived":false,"fork":false,"pushed_at":"2024-10-10T17:08:53.000Z","size":67,"stargazers_count":4,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-28T08:46:04.068Z","etag":null,"topics":["dragonruby","dragonruby-gtk","dragonrubygtk"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pvande.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":"support/testing.rb","governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["pvande"],"ko_fi":"pvande"}},"created_at":"2024-04-15T23:13:55.000Z","updated_at":"2024-12-26T18:15:05.000Z","dependencies_parsed_at":"2024-10-27T17:54:37.995Z","dependency_job_id":null,"html_url":"https://github.com/pvande/dragonborn","commit_stats":null,"previous_names":["pvande/dragonborn"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pvande%2Fdragonborn","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pvande%2Fdragonborn/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pvande%2Fdragonborn/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pvande%2Fdragonborn/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pvande","download_url":"https://codeload.github.com/pvande/dragonborn/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243918621,"owners_count":20368745,"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":["dragonruby","dragonruby-gtk","dragonrubygtk"],"created_at":"2024-10-09T20:18:08.548Z","updated_at":"2025-03-18T10:30:29.652Z","avatar_url":"https://github.com/pvande.png","language":"Ruby","funding_links":["https://github.com/sponsors/pvande","https://ko-fi.com/pvande"],"categories":[],"sub_categories":[],"readme":"# Dragonborn\n\n\u003e NOTE: Work in Progress!\n\nDragonborn is a drop-in library to sort out your DragonRuby game's\ninitialization. Works with DragonRuby 5.25+.\n\n|Version|\n|-------|\n|[![DragonRuby 5.25](https://img.shields.io/github/actions/workflow/status/pvande/dragonborn/5.25.yaml?style=flat\u0026label=5.25)](https://github.com/pvande/dragonborn/actions/workflows/5.25.yaml)|\n|[![DragonRuby 5.26](https://img.shields.io/github/actions/workflow/status/pvande/dragonborn/5.26.yaml?style=flat\u0026label=5.26)](https://github.com/pvande/dragonborn/actions/workflows/5.26.yaml)|\n|[![DragonRuby 5.27](https://img.shields.io/github/actions/workflow/status/pvande/dragonborn/5.27.yaml?style=flat\u0026label=5.27)](https://github.com/pvande/dragonborn/actions/workflows/5.27.yaml)|\n|[![DragonRuby 5.28](https://img.shields.io/github/actions/workflow/status/pvande/dragonborn/5.28.yaml?style=flat\u0026label=5.28)](https://github.com/pvande/dragonborn/actions/workflows/5.28.yaml)|\n|[![DragonRuby 5.29](https://img.shields.io/github/actions/workflow/status/pvande/dragonborn/5.29.yaml?style=flat\u0026label=5.29)](https://github.com/pvande/dragonborn/actions/workflows/5.29.yaml)|\n|[![DragonRuby 5.30](https://img.shields.io/github/actions/workflow/status/pvande/dragonborn/5.30.yaml?style=flat\u0026label=5.30)](https://github.com/pvande/dragonborn/actions/workflows/5.30.yaml)|\n|[![DragonRuby 5.32](https://img.shields.io/github/actions/workflow/status/pvande/dragonborn/5.32.yaml?style=flat\u0026label=5.32)](https://github.com/pvande/dragonborn/actions/workflows/5.32.yaml)|\n|[![DragonRuby 5.33](https://img.shields.io/github/actions/workflow/status/pvande/dragonborn/5.33.yaml?style=flat\u0026label=5.33)](https://github.com/pvande/dragonborn/actions/workflows/5.33.yaml)|\n|[![DragonRuby 5.34](https://img.shields.io/github/actions/workflow/status/pvande/dragonborn/5.34.yaml?style=flat\u0026label=5.34)](https://github.com/pvande/dragonborn/actions/workflows/5.34.yaml)|\n|[![DragonRuby 5.35](https://img.shields.io/github/actions/workflow/status/pvande/dragonborn/5.35.yaml?style=flat\u0026label=5.35)](https://github.com/pvande/dragonborn/actions/workflows/5.35.yaml)|\n|[![DragonRuby 5.36](https://img.shields.io/github/actions/workflow/status/pvande/dragonborn/5.36.yaml?style=flat\u0026label=5.36)](https://github.com/pvande/dragonborn/actions/workflows/5.36.yaml)|\n|[![DragonRuby 6.0](https://img.shields.io/github/actions/workflow/status/pvande/dragonborn/6.0.yaml?style=flat\u0026label=6.0)](https://github.com/pvande/dragonborn/actions/workflows/6.0.yaml)|\n|[![DragonRuby 6.1](https://img.shields.io/github/actions/workflow/status/pvande/dragonborn/6.1.yaml?style=flat\u0026label=6.1)](https://github.com/pvande/dragonborn/actions/workflows/6.1.yaml)|\n|[![DragonRuby 6.3](https://img.shields.io/github/actions/workflow/status/pvande/dragonborn/6.3.yaml?style=flat\u0026label=6.3)](https://github.com/pvande/dragonborn/actions/workflows/6.3.yaml)|\n\n## Rationale\n\nManaging `require` statements isn't fun.\n\nBuilding a game is complicated, and involves a lot of decisions that around\nlogic, aesthetics, timing, narrative, and many other factors. DragonRuby GTK\nprovides a fantastic platform for quickly building a prototype, and gradually\nevolving it into a publication-ready game — giving you creative freedom for both\nyour game and your code structure. As your game spreads out across multiple\nfiles, however, it becomes necessary for *this* file to use things from this\n*other* file, which affects the order that you have to `require` those files in…\n\nIt doesn't take many repetitions before remembering that order becomes a burden.\n\nIf you're familiar with Ruby from using Rails, you're probably used to having\nthe platform figure all that out for you. If you create a file named\n`person.rb` and that file contains a class named `Person`, you don't worry about\n`require` at all — you just _use `Person`_ wherever you need it. Combined with\nsomething like hotloading (where your code changes are automatically applied),\nyou can enter a powerful flow state where you stop editing *files* and start\nediting *the application directly*.\n\nDragonborn exists to bring that same workflow to DragonRuby, making it just that\n*little* bit easier to **finish your game**.\n\n## Usage\n\nStart by saving [dragonborn.rb] into your game's source tree. The entirety\nof Dragonborn's functionality is stored in that single file — DragonRuby even\nprovides [a built-in tool][download_stb] for doing this, which you can run from\nyour game's console:\n\n```ruby\n# Downloads to pvande/dragonborn/dragonborn.rb\n$gtk.download_stb_rb \"pvande\", \"dragonborn\", \"dragonborn.rb\"\n\n# OR\n\n# Downloads to wherever/you/want.rb\n$gtk.download_stb_rb_raw \"https://raw.githubusercontent.com/pvande/dragonborn/main/dragonborn.rb\", \"wherever/you/want.rb\"\n```\n\nFrom there, you can add the following lines to the top of your `app/main.rb`\nfile:\n\n``` ruby\nrequire \"pvande/dragonborn/dragonborn\"\n\nDragonborn.configure do\n  root \"app\"\nend\n```\n\nThis loads Dragonborn into your application, and directs it to manage and load\nall Ruby code within your `app` directory. By following the Dragonborn naming\nconventions, Dragonborn automatically works out an appropriate load order and\nrequires all of the files in its purview.\n\n---\n\nMany projects also have a \"junk drawer\" — one (or more!) directories of files\nthat contain the variety of helper functions, library patches, and other\nassorted code that your application relies on. For cases like these, Dragonborn\nalso includes a helper to just require each file.\n\n``` ruby\n# Requires all files in the \"patches\" directory, but not subdirectories.\nDragonborn.require_dir(\"patches\")\n```\n\nThese files are not considered \"managed\" by Dragonborn; it simply iterates\nthrough the files in that directory and `require`s them. This makes it well\nsuited for requiring files in bulk that may or may not follow convention, and\nwhich don't have load order considerations.\n\n### Convention\n\nFor Dragonborn to work with your project, simply name files and directories\nafter the classes, modules, and constants they define:\n\n``` text\n# Dragonborn.configure { root \"app\" }\n\napp/entity.rb                    -\u003e Entity\napp/entities/player.rb           -\u003e Entities::Player\napp/entities/player/sprite.rb    -\u003e Entities::Player::Sprite\napp/services/movement_service.rb -\u003e Services::MovementService\napp/scenes/settings/audio.rb     -\u003e Scenes::Settings::Audio\n```\n\n(In general, this follows the same naming conventions as Rails, if you're\nfamiliar with those.) File and directory names are expected to to be underscore\nseparated (e.g. `my_long_filename.rb`), and the corresponding constants will be\nin \"camel case\" (e.g. `MyLongFilename`).\n\n**If your project doesn't align perfectly with this convention**, that's fine!\nDragonborn also provides configuration hooks to allow most projects to take\nadvantage for some or all of their code.\n\n### Concepts\n\n#### Root Directories\nIn the above example, each file path was mapped to a constant name **based on the\nfile path relative to `app`**. This is because we configured Dragonborn to use\n`app` as a *root directory*, which maps all of its descendants relative to the\ntop-level Ruby namespace (`Object`).\n\n* Dragonborn can be configured with any number of root directories, and will take\n  responsibilty for loading code from all of them.\n  * If you do not configure *any* root directories, Dragonborn won't do anything.\n* Dependencies between code in different root directories will be automatically\n  resolved correctly.\n* `app` is not required to be a root directory.\n* Root directories may be nested.\n  * In the previous example, we could add a second root directory for\n    `app/entities`. Doing so would remove the `Entities` namespace from `Player`\n    and `Player::Sprite`, but leave the other mappings unaffected.\n\n#### Inflections\nWhen mapping a file or directory name to a constant name, Dragonborn takes the\nsimple approach of breaking apart the filename into words, capitalizing each\nword, and joining them back together again. This works reasonably well in many\ncases, but exceptions always exist. One such class of exceptions are acronyms,\nwhich would look ridiculous written out with underscores (e.g.\n`h_t_t_p_server.rb`) but can also feel wrong written in with only initial\ncaptial letters (e.g. `Ui::Widget`).\n\nTo help resolve this, Dragonborn uses an *inflection map* to override how it\nhandles specific parts of a name (`html` =\u003e `HTML`) or entire names\n(`cutscene_keyframes` =\u003e `CUTSCENE_KEYFRAMES`).\n\n### Configuration\n\nInside the block passed to `Dragonborn.configure`, you tell Dragonborn how your\nproject is structured by calling the following configuration functions.\n\n#### root [dir]\nThe `root` configuration option indicates that this is a directory Dragonborn\nshould autoload source code from. This option may be supplied multiple times,\nand may overlap each other.\n\nSpecifying `root` directories closer to the files being required will\neffectively \"shrink\" the expected Ruby constant name.\n\n``` ruby\n# Given a file at \"app/components/player/movement.rb\"…\n\nDragonborn.configure { root \"app\" }\n# … expects to load `Components::Player::Movement`\n\nDragonborn.configure { root \"app/components\" }\n# … expects to load `Player::Movement`\n\nDragonborn.configure { root \"app/components/player\" }\n# … expects to load `Movement`\n```\n\nIn cases where the same file belongs to multiple `root` directories, Dragonborn\nwill always choose the `root` resulting in the shortest constant name.\n\nWith multiple `root` directories, it is possible to have multiple files map to\nthe same constant. Dragonborn automatically detects this case, and will not proceed.\n\n#### ignore [filepath]\nThe `ignore` configuration option instructs Dragonborn to skip autoloading the\nnamed file. This can be useful when dealing with files that don't implicitly\ncreate a constant, or don't follow Dragonborn's conventions.\n\n`app/main` is implicitly ignored.\n\n#### inflection [word] =\u003e [Word]\n#### inflection [underscored_name] =\u003e [CamelCasedName]\nCustom `inflection`s can be defined as well — these give Dragonborn hints about\nhow to transform your filenames into Ruby constant names.\n\n``` ruby\n# Given a file at \"app/ui/button.rb\"…\n\nDragonborn.configure { root \"app\" }\n# … expects to load `Ui::Button\n\nDragonborn.configure do\n  root \"app\"\n  inflection \"ui\" =\u003e \"UI\"\nend\n# … expects to load `UI::Button`\n```\n\n### Caveats\n\n* Dragonborn has a constant lookup algorithm that is *slightly* different than\n  what's described by the Ruby standard. Specifically, Ruby's constant lookup\n  searches the ancestors of *each open namespace*. This is reasonably intuitive\n  as you're writing code, but makes metaprogramming life more difficult. (For an\n  example, see [caveats/examples/constant_lookup.rb].)\n\n  Dragonborn works by exploiting the `const_missing` functionality in Ruby to\n  load your code as it's needed. In doing so, any information about which\n  namespaces were open at the location that the const was looked up is lost.\n  Since that information is required to perfectly replicate Ruby's native\n  constant lookup, **Dragonborn will find different constants in some cases**.\n\n  To minimize the odds of running afoul of this behavior, avoid defining your\n  classes and modules with the \"compact\" (`::`-separated) syntax, and be mindful\n  about constant lookups in dynamically defined methods.\n\n  ``` ruby\n  # AVOID:\n  class Foo::Bar::Baz\n    # Constants looked up here will resolve incorrectly in Dragonborn!\n  end\n\n  # INSTEAD:\n  class Foo\n    class Bar\n      class Baz\n        # This is more verbose and more indentation, but Dragonborn's behavior\n        # will match Ruby's!\n      end\n    end\n  end\n\n  # --- #\n\n  # AVOID:\n  Foo.define_method(:bar) { CONST }\n\n  # INSTEAD:\n  class Foo\n    define_method(:bar) { CONST }\n  end\n\n  # --- #\n\n  # AVOID:\n  def Foo.bar\n    CONST\n  end\n\n  # INSTEAD:\n  class Foo\n    def self.bar\n      CONST\n    end\n  end\n  ```\n\n[dragonborn.rb]: https://raw.githubusercontent.com/pvande/dragonborn/main/dragonborn.rb\n[download_stb]: https://docs.dragonruby.org/#/api/runtime?id=download_stb_rb_raw\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpvande%2Fdragonborn","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpvande%2Fdragonborn","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpvande%2Fdragonborn/lists"}