{"id":22981025,"url":"https://github.com/rubyworks/fileutils2","last_synced_at":"2025-09-04T06:41:49.868Z","repository":{"id":7530042,"uuid":"8881637","full_name":"rubyworks/fileutils2","owner":"rubyworks","description":"FileUtils refactored","archived":false,"fork":false,"pushed_at":"2013-03-20T20:51:23.000Z","size":240,"stargazers_count":3,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-08-06T10:34:47.926Z","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/rubyworks.png","metadata":{"files":{"readme":"README.md","changelog":"HISTORY.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-03-19T15:14:22.000Z","updated_at":"2019-08-13T15:17:54.000Z","dependencies_parsed_at":"2022-08-27T18:20:42.265Z","dependency_job_id":null,"html_url":"https://github.com/rubyworks/fileutils2","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/rubyworks/fileutils2","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rubyworks%2Ffileutils2","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rubyworks%2Ffileutils2/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rubyworks%2Ffileutils2/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rubyworks%2Ffileutils2/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rubyworks","download_url":"https://codeload.github.com/rubyworks/fileutils2/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rubyworks%2Ffileutils2/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273567273,"owners_count":25128630,"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","status":"online","status_checked_at":"2025-09-04T02:00:08.968Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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-12-15T01:46:45.605Z","updated_at":"2025-09-04T06:41:49.657Z","avatar_url":"https://github.com/rubyworks.png","language":"Ruby","readme":"# FileUtils2\n\n*A Refactorization of Ruby's Standard FileUtils Library*\n\n[Homepage](http://rubyworks.github.com/fileutils2) /\n[Documentation](http://rubydoc.info/gems/fileutils2) /\n[Report Issue](http://github.com/rubyworks/fileutils2/issues) /\n[Source Code](http://github.com/rubyworks/fileutils2)\n\n[![Build Status](https://secure.travis-ci.org/rubyworks/fileutils2.png)](http://travis-ci.org/rubyworks/fileutils2)\n[![Gem Version](https://badge.fury.io/rb/fileutils2.png)](http://badge.fury.io/rb/fileutils2) \u0026nbsp; \u0026nbsp;\n[![Flattr Me](http://api.flattr.com/button/flattr-badge-large.png)](http://flattr.com/thing/324911/Rubyworks-Ruby-Development-Fund)\n\n\n## About\n\nFileUtils as provided in Ruby suffers from the following design issues:\n\n1. By using `module_function` FileUtils creates two copies of every method.\n   Overriding an instance method will not override the corresponding class\n   method, and vice-versa.\n\n2. The design makes it inordinately more difficult to properly extend \n   FileUtils than it needs to be, because one has to manually ensure any\n   new method added to FileUtils are also added to the submodules.\n\n3. The meta-programming aspect of the design requires the direct modification\n   of a constant, `OPT_TABLE`.\n\n4. Ruby's Module Inclusion Problem prevents extension modules from being included\n   into FileUtils without additional steps being taken to include the module\n   in every submodule as well.\n\nLets take a simple example. Lets say we want to add a recursive linking\nmethod. \n\n```ruby\n    module FileUtils\n      def ln_r(dir, dest, options={})\n        ...\n      end\n      module_function :ln_r\n  end\n```\n\nThat would seem like the right code, would it not? Unfortunately you would be\nway off the mark. Instead one would need to do the following:\n\n```ruby\n    module FileUtils\n      OPT_TABLE['ln_r'] = [:force, :noop, :verbose]\n\n      def ln_r(dir, dest, options={})\n        fu_check_options options, OPT_TABLE['ln_r']\n        ...\n      end\n      module_function :ln_r\n\n      module Verbose\n        include FileUtils\n        module_eval(\u003c\u003c-EOS, __FILE__, __LINE__ + 1)\n          def ln_r(*args)\n            super(*fu_update_option(args, :verbose =\u003e true))\n          end\n          private :ln_r\n        EOS\n        extend self\n      end\n\n      module NoWrite\n        include FileUtils\n        module_eval(\u003c\u003c-EOS, __FILE__, __LINE__ + 1)\n          def ln_r(*args)\n            super(*fu_update_option(args, :noop =\u003e true))\n          end\n          private :ln_r\n        EOS\n        extend self\n      end\n\n      module DryRun\n        include FileUtils\n        module_eval(\u003c\u003c-EOS, __FILE__, __LINE__ + 1)\n          def ln_r(*args)\n            super(*fu_update_option(args, :noop =\u003e true, :verbose =\u003e true))\n          end\n          private :ln_r\n        EOS\n        extend self\n      end\n    end\n```\n\nFileUtils2 fixes all this by doing three thing:\n\n1. Use `self extend` instead of `module_function`.\n2. Overriding `#include` to ensure inclusion at all levels.\n3. Define a single *smart* DSL method called, #define_command`.\n\nWith these changes the above code becomes simply:\n\n```ruby\n    module FileUtils2\n      def ln_r(dir, dest, options={})\n        fu_check_options options, OPT_TABLE['ln_r']\n        ...\n      end\n\n      define_command('ln_r', :force, :noop, :verbose)\n    end\n```\n\nNotice we still check the `OPT_TABLE` to ensure only the supported options\nare provided. So there is still room for some improvement in the design.\nThis \"second phase\" will come later, after the initial phase has been put \nthrough its paces. (At least, that was the plan. See \"Why a Gem\" below.)\n\nAlso note that this refactorization does not change the underlying functionality\nor the FileUtils methods in any way. They remain the same as in Ruby's standard\nlibrary.\n\n\n## Mixins for FileUtils\n\nOf course it might be unusual to want to include a mixin module into FileUtils.\nNonetheless it should still be possible and not be inordinately mind-boggling\nto do so. With FileUtils2 it is almost as easy as simple as just including the\nmixin module. But there is still the `define_command` method that must be\ninvoked. Thankfully it is not difficult to get that call in.\n\nFor the sake of example lets say the above `#ln_r` method is nicely namespaced\nin it's own module and we wish to extend FileUtils with it.\n\n```ruby\n    module MyApp::FileUtilsMixin\n      def self.included(fileutils)\n        fileutils.module_eval do\n          define_command('ln_r', :force, :noop, :verbose)\n        end\n      end\n\n      def ln_r(dir, dest, options={})\n        fu_check_options options, OPT_TABLE['ln_r']\n        ...\n      end\n    end\n\n    module FileUtils\n      include MyApp::FileUtilsMixin\n    end\n```\n\nNotice that defining the `included` callback, we were able to invoke the\n`define_command` method on the FileUtils module just as if we were adding\nthe new method directly.\n\n\n## Overriding FileUtils\n\nYou can use FileUtils2 in place of FileUtils simple by setting FileUtils\nequal to FileUtils2.\n\n```ruby\n    require 'fileutils2'\n    FileUtils = FileUtils2\n```\n\nIt will issue a warning if FileUtils is already loaded, but it should work fine\nin either case. In fact, it may be wise to first `require 'fileutils'` in anycase\nto make sure it's not loaded later by some other script, which could cause some\nunspecified results due to method clobbering. Of course there should plenty\nof warnings in the output in that case, so you could just keep an eye out for\nit instead.\n\nFor the sake of simply being overly thurough, included in the gem is a script\nthat takes care of most of this for you called, `override.rb`.\n\n```ruby\n    require 'fileutils2/override'\n```\n\nIt requires fileutils2.rb for you and sets `FileUtils = FileUtils2` while\nsupressing the usual warning. It doesn't preload the old fileutils.rb library\nfirst though. That's your call.\n\n\n## JRuby and Rubinius Users\n\nFileUtils2, as well as the original FileUtils library for that matter, produce\na few test failures (out of a 1000+) when run again JRuby or Rubinius. At this\npoint it is unclear exactly what the issues are. If you are involved in either\nof these projects and can spare a little time to try and fix these issues, that\nwould be really great of you! Have a look at the\n[Rubinius build](https://travis-ci.org/rubyworks/fileutils2/jobs/5634466)\nand the [JRuby build](https://travis-ci.org/rubyworks/fileutils2/jobs/5634467)\nfor these test results.\n\n\n## Why a Gem?\n\nYou might be wondering why this is a Gem and not part of Ruby's standard library.\nUnfortunately, due to to what I believe to be nothing more than \"clique politics\"\namong some of the  Ruby Core members, this code has been rejected.\n\nActually it was accepted, but after the discovery a bug (easily fixed) it was\nreverted. Despite the code passing all tests, and the fact that this bug made it\nclear that the tests themselves were missing something (that's a good thing to \ndiscover!), the code was reverted to the old design. Sadly, I am certain there\nwas no other reason for it than the simple fact that the three main core members\nfrom Seattle.rb begrudge me, and go out their way to undermine everything I do.\nThis behavior is fairly well documented in the archives of the ruby-talk mailing\nlist. I don't like to think that their personal opinions of me would influence\nthe design of the Ruby programming language, which should be of the utmost\nprofessional character, but it is clearly not the case, as is evidenced by\nthe fact that they were not willing to discuss the design, let alone actually fix\nit, but instead summarily declared themselves the new maintainers of the code,\nreverted the code to the old design and pronounced the issue closed. Period.\n\n* https://bugs.ruby-lang.org/issues/4970\n* https://bugs.ruby-lang.org/issues/7958\n\n\n## Legal\n\nCopyright (c) 2011 Rubyworks\n\nCopyright (c) 2000 Minero Aoki\n\nThis program is distributed under the terms of the\n[BSD-2-Clause](http://www.spdx.org/licenses/BSD-2-Clause) license.\n\nSee LICENSE.txt file for details.\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frubyworks%2Ffileutils2","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frubyworks%2Ffileutils2","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frubyworks%2Ffileutils2/lists"}