{"id":20460959,"url":"https://github.com/ericgj/ing","last_synced_at":"2025-03-05T11:27:20.971Z","repository":{"id":4643199,"uuid":"5788280","full_name":"ericgj/ing","owner":"ericgj","description":"Vanilla ruby command-line scripting","archived":false,"fork":false,"pushed_at":"2012-10-07T05:18:02.000Z","size":524,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-17T05:02:51.565Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ericgj.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2012-09-13T01:02:26.000Z","updated_at":"2014-05-04T19:48:58.000Z","dependencies_parsed_at":"2022-08-06T17:16:32.145Z","dependency_job_id":null,"html_url":"https://github.com/ericgj/ing","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericgj%2Fing","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericgj%2Fing/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericgj%2Fing/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericgj%2Fing/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ericgj","download_url":"https://codeload.github.com/ericgj/ing/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242017402,"owners_count":20058422,"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":[],"created_at":"2024-11-15T12:22:38.341Z","updated_at":"2025-03-05T11:27:20.951Z","avatar_url":"https://github.com/ericgj.png","language":"Ruby","readme":"﻿# Ing\n## Vanilla ruby command-line scripting.\n\nor gratuitous backronym: \u003cb\u003eI\u003c/b\u003e \u003cb\u003eN\u003c/b\u003eeed a \u003cb\u003eG\u003c/b\u003eenerator! \n\nIng is a task scripting micro-toolkit designed around the following opinions: \n\n- Ruby itself is a domain-specific language for scripting (among other things), \nit has great facilities for dealing with filesystems, processes, network IO, \ninteracting with the shell, etc;\n\n- In addition, Ruby's object model gives you most of what you need for \norganizing your code into tasks to be run from the command line, for dependency \nmanagement, handling errors, etc.\n\n- Sometimes the functionality your tasks implement you want to make use of \nwithin other programs, not only from the shell. You don't want to have to \neither (a) wade through the scripting framework to figure out how to get to it, \nor (b) refactor your tasks into separate modules and classes.\n\n- In particular, you want to be able to test your tasks independently of the\nframework.\n\n- A framework (any framework, in any context) should not encourage bad design \nat the expense of supposed simplicity of the interface. A framework should \nget out of the way as much as possible.\n\n## Introduction\n\nThe core of what Ing provides is a _router_ and built-in _option parser_ (using \nthe venerable and excellent [Trollop](http://trollop.rubyforge.org/)) that maps \nthe command line to your ruby classes and methods, using simple conventions.\n\nFor example, this:\n\n```bash\ning some:task run something --verbose\n```\n\nin the most typical scenario, routes to:\n\n```ruby\nSome::Task.new(:verbose =\u003e true).run(\"something\")\n```\n\nor if Some::Task is not a class but a proc or other 'callable', routes to:\n\n```ruby\nSome::Task.call(:run, \"something\", :verbose =\u003e true)\n```\n\nAs you can see, although the implementation is completely different, the \ncommand-line syntax is similar to Thor's. \n\nIn addition, Ing includes Thor's (Rails') generator methods and conventions \nso you can do things like this within your tasks:\n\n```ruby\nif yes? 'process foo files?', :yellow\n  inside('foo') { create_file '%foo_file%.rb' }\nend\n``` \n\nUnlike Thor or Rake, Ing does not define its own DSL. Your tasks correspond \nto plain ruby objects and methods. Ing just handles routing from the command \nline to them, and setting options. Your classes or procs do the rest.\n\nAs we will see, there are some base classes your tasks can inherit from that \ncut down on boilerplate code for common scenarios, but they are there only for \nconvenience: your task classes/procs are not required to be coupled to the \nframework at all.\n\n[MORE](ing/blob/master/SYNTAX.md)\n\n## Installation\n\n    gem install ing\n    \nTo generate a default `ing.rb` file (similar to Rakefile or Thorfile), that \nloads from a `tasks` directory:\n    \n    ing setup\n    \n## Usage\n\n### Built-in commands\n\nIng has some built-in commands. You can see what they are (so far) with \n`ing list -n ing:commands`.  And you can get help on a command with \n`ing help ...`.\n\n**NEW**: Ing now provides bash auto-completion (hooray!). Copy \n[the script](ing/blob/master/completions/ing.bash) to your OS' bash-completions directory, or source it manually, and tab away.\n\n### Generator tasks\n\nThe most significant built-in Ing command is `generate` or `g`, which\nsimplifies a common and familiar use-case (at the expense of some file-\nsystem conventions):\n\n    ing generate some:task --force\n\nUnlike Thor/Rails generators, these don't need to be packaged up as gems\nand preloaded into ruby. They can be parsed as either:\n\n  1. A __file__ relative to a root dir: e.g. __some/task__, or\n  2. A __subdirectory__ of the root dir, in which case it attempts to\n  preload `ing.rb` within that subdirectory: e.g. __some/task/ing.rb__\n\nSo the command above is then dispatched as normal to \n`Some::Task.new(:force =\u003e true).call`  (`#call` is used if no method is\nspecified). So you should put the task code within that namespace in the\npreloaded file.\n\n(By default, the generator root directory is specified by \n`ENV['ING_GENERATORS_ROOT']` or failing that, `~/.ing/generators`.)\n\n[MORE](#)\n\n### A simple example of a plain old ruby task\n\nLet's say you want to run your project's tests with a command like `ing test`.\nThe default is to run the whole suite; but if you just want unit tests you can\nsay `ing test unit`. This is what it would look like (in `./ing.rb`):\n\n```ruby\nclass Test\n\n  # no options passed, but you need the constructor\n  def initialize(options); end\n  \n  # `ing test`\n  def call(*args)\n    suite\n  end\n  \n  # `ing test suite`\n  def suite\n    unit; functional; acceptance\n  end\n\n  # `ing test unit`\n  def unit\n    type 'unit'\n  end\n\n  # `ing test functional`\n  def functional\n    type 'functional'\n  end\n\n  # `ing test acceptance`\n  def acceptance\n    type 'acceptance'\n  end\n    \n  def type(dir)\n    Dir[\"./test/#{dir}/*.rb\"].each { |f| require_relative f }\n  end\n  \nend\n```\n    \nAs you can see, the second arg corresponds to the method name. `call` is what\ngets called when there is no second arg.  Organizing the methods like this means\nyou can also do `ing test type custom`: extra non-option arguments are passed \ninto the method as parameters.  \n\nNote in most real cases you would want to namespace your tasks, and not use\na top-level class named Test (which would fail in some ruby versions in fact). \nThis is just to give you a flavor.\n\nFor more worked examples of ing tasks, see the \n[examples](ing/blob/master/examples) directory.\n\n[MORE](ing/blob/master/TASKS.md)\n\n### Option arguments\n\nYour tasks (ing subcommands) can specify what options they take by defining a \nclass method `specify_options`.  For example:\n\n```ruby\nclass Cleanup\n\n  def self.specify_options(spec)\n    spec.text \"Clean up your path\"\n    spec.text \"\\nUsage:\"\n    spec.text \"ing cleanup [OPTIONS]\"\n    spec.text \"\\nOptions:\"\n    spec.opt :quiet, \"Run silently\"\n    spec.opt :path,  \"Path to clean up\", :type =\u003e :string, :default =\u003e '.'\n  end\n    \n  attr_accessor :options\n  \n  def initialize(options)\n    self.options = options\n  end\n  \n  # ...\nend\n```\n\nThe syntax used in `self.specify_options` is Trollop - in fact what you are \ndoing is building a `Trollop::Parser` which then sends the parsed options into \nyour constructor. \n\nIn general your constructor should just save the options to\nan instance variable like this, but in some cases you might want to do further\nprocessing of the passed options.\n\n[MORE](ing/blob/master/OPTIONS.md)\n\n### Using the Ing::Task base class\n\nTo save some boilerplate, and to allow more flexible options specification, \nas well as a few more conveniences, you can inherit from `Ing::Task` and \nrewrite this example as:\n\n```ruby\nclass Cleanup \u003c Ing::Task\n  desc \"Clean up your path\"\n  usage \"ing cleanup [OPTIONS]\"\n  opt :quiet, \"Run silently\"\n  opt :path,  \"Path to clean up\", :type =\u003e :string, :default =\u003e '.'\n\n  # ...\nend\n```\n\nThis gives you a slightly more automated help message, with the description\nlines followed by usage followed by options, and with headers for each section.\n\n`Ing::Task` also lets you inherit options. Say you have another task:\n\n```ruby\nclass BigCleanup \u003c Cleanup\n  opt :servers, \"On servers\", :type =\u003e :string, :multi =\u003e true\nend\n```\n\nThis task will have the two options from its superclass as well as its own. \n(Note the description and usage lines are _not_ inherited this way, only the \noptions).\n\n### Generator tasks\n\nIf you want to use Thor-ish generator methods, your task classes need a few more\nthings added to their interface. Basically, it should look something like this.\n\n```ruby\nclass MyGenerator\n\n  def self.specify_options(spec)\n    # ...\n  end\n  \n  include Ing::Files\n  \n  attr_accessor :destination_root, :source_root, :options, :shell\n  \n  # default == execution from within your project directory\n  def destination_root\n    @destination_root ||= Dir.pwd\n  end\n  \n  # default == current file is within root directory of generator files\n  def source_root\n    @source_root ||= File.expand_path(File.dirname(__FILE__))\n  end\n  \n  def initialize(options)\n    self.options = options\n  end\n  \n  # ...\nend\n```\n\nThe generator methods need `:destination_root`, `:source_root`, and `:shell`.\nAlso, `include Ing::Files` _after_ you specify any options (this is because\n`Ing::Files` adds several options automatically).\n\nIf you prefer, you can inherit from `Ing::Generator`, which gives you all of \nthe above defaults more or less, plus the functionality of `Ing::Task`.\n\nLike `Ing::Task`, `Ing::Generator` is simply a convenience for common scenarios.\n\n[MORE](ing/blob/master/GENERATORS.md)\n\n## Standalone executables\n\nYou can use Ing to generate 'standalone' executables from your tasks, so you\ncan call it directly from the command line and also redistribute it (as a gem).\nFor more details see `ing help gemify`.\n\n## Motivation\n\nI wanted to use Thor's generator methods and shell conventions to write my own\ngenerators. But I didn't want to fight against Thor's hijacking of ruby classes.\n\nI love Rake, but find it much too easy to write horribly unmaintainable code in\nits DSL, and always fight with its nonstandard command-line syntax.\n\n## Q \u0026 A\n\n### But what about task dependency resolution?\n\nThat's what `require` and `||=` are for ;)\n\nSeriously, you do have `Ing.invoke Some::Task, :some_method` if you want a \ndeclarative way to say, from any point in your codebase, that you only want the\ndepended-on task to run only if it hasn't already. \n\nBut before you do, please consider:\n\n- If your case is _invoking a task only once within the same module_, you \nshould probably simply design your methods so they are called that way in plain \nruby.\n\n- If your case is _running some bit of setup code_ that is shared among several \ntasks that would otherwise _not_ be executed as a task itself, `Ing.invoke` is\noverkill. The code should be refactored so that it's accessible to the several\ntasks, but not implemented as a task itself.\n\n`Ing.invoke` is there for cases of multi-step tasks where you want access to\nboth the complete task and the sub-steps: such as mult-step compilation, the \nclassic use-case for `make`.\n\nIn fact, if you find yourself needing to use `Ing.invoke` a lot, perhaps you \nshould just use `make`, since the DSL is optimized for exactly this kind of \ntask.\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fericgj%2Fing","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fericgj%2Fing","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fericgj%2Fing/lists"}