amp 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/.hgignore +26 -0
- data/AUTHORS +2 -0
- data/History.txt +6 -0
- data/LICENSE +37 -0
- data/MANIFESTO +7 -0
- data/Manifest.txt +294 -0
- data/README.md +129 -0
- data/Rakefile +102 -0
- data/SCHEDULE.markdown +12 -0
- data/STYLE +27 -0
- data/TODO.markdown +149 -0
- data/ampfile.rb +47 -0
- data/bin/amp +30 -0
- data/bin/amp1.9 +30 -0
- data/ext/amp/bz2/README.txt +39 -0
- data/ext/amp/bz2/bz2.c +1582 -0
- data/ext/amp/bz2/extconf.rb +77 -0
- data/ext/amp/bz2/mkmf.log +29 -0
- data/ext/amp/mercurial_patch/extconf.rb +5 -0
- data/ext/amp/mercurial_patch/mpatch.c +405 -0
- data/ext/amp/priority_queue/extconf.rb +5 -0
- data/ext/amp/priority_queue/priority_queue.c +947 -0
- data/ext/amp/support/extconf.rb +5 -0
- data/ext/amp/support/support.c +250 -0
- data/lib/amp.rb +200 -0
- data/lib/amp/commands/command.rb +507 -0
- data/lib/amp/commands/command_support.rb +137 -0
- data/lib/amp/commands/commands/config.rb +143 -0
- data/lib/amp/commands/commands/help.rb +29 -0
- data/lib/amp/commands/commands/init.rb +10 -0
- data/lib/amp/commands/commands/templates.rb +137 -0
- data/lib/amp/commands/commands/version.rb +7 -0
- data/lib/amp/commands/commands/workflow.rb +28 -0
- data/lib/amp/commands/commands/workflows/git/add.rb +65 -0
- data/lib/amp/commands/commands/workflows/git/copy.rb +27 -0
- data/lib/amp/commands/commands/workflows/git/mv.rb +23 -0
- data/lib/amp/commands/commands/workflows/git/rm.rb +60 -0
- data/lib/amp/commands/commands/workflows/hg/add.rb +53 -0
- data/lib/amp/commands/commands/workflows/hg/addremove.rb +86 -0
- data/lib/amp/commands/commands/workflows/hg/annotate.rb +46 -0
- data/lib/amp/commands/commands/workflows/hg/archive.rb +126 -0
- data/lib/amp/commands/commands/workflows/hg/branch.rb +28 -0
- data/lib/amp/commands/commands/workflows/hg/branches.rb +30 -0
- data/lib/amp/commands/commands/workflows/hg/bundle.rb +115 -0
- data/lib/amp/commands/commands/workflows/hg/clone.rb +95 -0
- data/lib/amp/commands/commands/workflows/hg/commit.rb +42 -0
- data/lib/amp/commands/commands/workflows/hg/copy.rb +31 -0
- data/lib/amp/commands/commands/workflows/hg/debug/dirstate.rb +32 -0
- data/lib/amp/commands/commands/workflows/hg/debug/index.rb +36 -0
- data/lib/amp/commands/commands/workflows/hg/default.rb +9 -0
- data/lib/amp/commands/commands/workflows/hg/diff.rb +30 -0
- data/lib/amp/commands/commands/workflows/hg/forget.rb +11 -0
- data/lib/amp/commands/commands/workflows/hg/heads.rb +25 -0
- data/lib/amp/commands/commands/workflows/hg/identify.rb +23 -0
- data/lib/amp/commands/commands/workflows/hg/import.rb +135 -0
- data/lib/amp/commands/commands/workflows/hg/incoming.rb +85 -0
- data/lib/amp/commands/commands/workflows/hg/info.rb +18 -0
- data/lib/amp/commands/commands/workflows/hg/log.rb +21 -0
- data/lib/amp/commands/commands/workflows/hg/manifest.rb +13 -0
- data/lib/amp/commands/commands/workflows/hg/merge.rb +53 -0
- data/lib/amp/commands/commands/workflows/hg/move.rb +28 -0
- data/lib/amp/commands/commands/workflows/hg/outgoing.rb +61 -0
- data/lib/amp/commands/commands/workflows/hg/pull.rb +74 -0
- data/lib/amp/commands/commands/workflows/hg/push.rb +20 -0
- data/lib/amp/commands/commands/workflows/hg/remove.rb +45 -0
- data/lib/amp/commands/commands/workflows/hg/resolve.rb +83 -0
- data/lib/amp/commands/commands/workflows/hg/revert.rb +53 -0
- data/lib/amp/commands/commands/workflows/hg/root.rb +13 -0
- data/lib/amp/commands/commands/workflows/hg/serve.rb +38 -0
- data/lib/amp/commands/commands/workflows/hg/status.rb +116 -0
- data/lib/amp/commands/commands/workflows/hg/tag.rb +69 -0
- data/lib/amp/commands/commands/workflows/hg/tags.rb +27 -0
- data/lib/amp/commands/commands/workflows/hg/tip.rb +13 -0
- data/lib/amp/commands/commands/workflows/hg/update.rb +27 -0
- data/lib/amp/commands/commands/workflows/hg/verify.rb +9 -0
- data/lib/amp/commands/commands/workflows/hg/view.rb +36 -0
- data/lib/amp/commands/dispatch.rb +181 -0
- data/lib/amp/commands/hooks.rb +81 -0
- data/lib/amp/dependencies/amp_support.rb +1 -0
- data/lib/amp/dependencies/amp_support/ruby_amp_support.rb +103 -0
- data/lib/amp/dependencies/minitar.rb +979 -0
- data/lib/amp/dependencies/priority_queue.rb +18 -0
- data/lib/amp/dependencies/priority_queue/c_priority_queue.rb +1 -0
- data/lib/amp/dependencies/priority_queue/poor_priority_queue.rb +46 -0
- data/lib/amp/dependencies/priority_queue/ruby_priority_queue.rb +525 -0
- data/lib/amp/dependencies/python_config.rb +211 -0
- data/lib/amp/dependencies/trollop.rb +713 -0
- data/lib/amp/dependencies/zip/ioextras.rb +155 -0
- data/lib/amp/dependencies/zip/stdrubyext.rb +111 -0
- data/lib/amp/dependencies/zip/tempfile_bugfixed.rb +186 -0
- data/lib/amp/dependencies/zip/zip.rb +1850 -0
- data/lib/amp/dependencies/zip/zipfilesystem.rb +609 -0
- data/lib/amp/dependencies/zip/ziprequire.rb +90 -0
- data/lib/amp/encoding/base85.rb +97 -0
- data/lib/amp/encoding/binary_diff.rb +82 -0
- data/lib/amp/encoding/difflib.rb +166 -0
- data/lib/amp/encoding/mercurial_diff.rb +378 -0
- data/lib/amp/encoding/mercurial_patch.rb +1 -0
- data/lib/amp/encoding/patch.rb +292 -0
- data/lib/amp/encoding/pure_ruby/ruby_mercurial_patch.rb +123 -0
- data/lib/amp/extensions/ditz.rb +41 -0
- data/lib/amp/extensions/lighthouse.rb +167 -0
- data/lib/amp/graphs/ancestor.rb +147 -0
- data/lib/amp/graphs/copies.rb +261 -0
- data/lib/amp/merges/merge_state.rb +164 -0
- data/lib/amp/merges/merge_ui.rb +322 -0
- data/lib/amp/merges/simple_merge.rb +450 -0
- data/lib/amp/profiling_hacks.rb +36 -0
- data/lib/amp/repository/branch_manager.rb +234 -0
- data/lib/amp/repository/dir_state.rb +950 -0
- data/lib/amp/repository/journal.rb +203 -0
- data/lib/amp/repository/lock.rb +207 -0
- data/lib/amp/repository/repositories/bundle_repository.rb +214 -0
- data/lib/amp/repository/repositories/http_repository.rb +377 -0
- data/lib/amp/repository/repositories/local_repository.rb +2661 -0
- data/lib/amp/repository/repository.rb +94 -0
- data/lib/amp/repository/store.rb +485 -0
- data/lib/amp/repository/tag_manager.rb +319 -0
- data/lib/amp/repository/updatable.rb +532 -0
- data/lib/amp/repository/verification.rb +431 -0
- data/lib/amp/repository/versioned_file.rb +475 -0
- data/lib/amp/revlogs/bundle_revlogs.rb +246 -0
- data/lib/amp/revlogs/changegroup.rb +217 -0
- data/lib/amp/revlogs/changelog.rb +338 -0
- data/lib/amp/revlogs/changeset.rb +521 -0
- data/lib/amp/revlogs/file_log.rb +165 -0
- data/lib/amp/revlogs/index.rb +493 -0
- data/lib/amp/revlogs/manifest.rb +195 -0
- data/lib/amp/revlogs/node.rb +18 -0
- data/lib/amp/revlogs/revlog.rb +1032 -0
- data/lib/amp/revlogs/revlog_support.rb +126 -0
- data/lib/amp/server/amp_user.rb +44 -0
- data/lib/amp/server/extension/amp_extension.rb +396 -0
- data/lib/amp/server/extension/authorization.rb +201 -0
- data/lib/amp/server/fancy_http_server.rb +252 -0
- data/lib/amp/server/fancy_views/_browser.haml +28 -0
- data/lib/amp/server/fancy_views/_diff_file.haml +13 -0
- data/lib/amp/server/fancy_views/_navbar.haml +17 -0
- data/lib/amp/server/fancy_views/changeset.haml +31 -0
- data/lib/amp/server/fancy_views/commits.haml +32 -0
- data/lib/amp/server/fancy_views/file.haml +35 -0
- data/lib/amp/server/fancy_views/file_diff.haml +23 -0
- data/lib/amp/server/fancy_views/harshcss/all_hallows_eve.css +72 -0
- data/lib/amp/server/fancy_views/harshcss/amy.css +147 -0
- data/lib/amp/server/fancy_views/harshcss/twilight.css +138 -0
- data/lib/amp/server/fancy_views/stylesheet.sass +175 -0
- data/lib/amp/server/http_server.rb +140 -0
- data/lib/amp/server/repo_user_management.rb +287 -0
- data/lib/amp/support/amp_config.rb +164 -0
- data/lib/amp/support/amp_ui.rb +287 -0
- data/lib/amp/support/docs.rb +54 -0
- data/lib/amp/support/generator.rb +78 -0
- data/lib/amp/support/ignore.rb +144 -0
- data/lib/amp/support/loaders.rb +93 -0
- data/lib/amp/support/logger.rb +103 -0
- data/lib/amp/support/match.rb +151 -0
- data/lib/amp/support/multi_io.rb +87 -0
- data/lib/amp/support/openers.rb +121 -0
- data/lib/amp/support/ruby_19_compatibility.rb +66 -0
- data/lib/amp/support/support.rb +1095 -0
- data/lib/amp/templates/blank.commit.erb +23 -0
- data/lib/amp/templates/blank.log.erb +18 -0
- data/lib/amp/templates/default.commit.erb +23 -0
- data/lib/amp/templates/default.log.erb +26 -0
- data/lib/amp/templates/template.rb +165 -0
- data/site/Rakefile +24 -0
- data/site/src/about/ampfile.haml +57 -0
- data/site/src/about/commands.haml +106 -0
- data/site/src/about/index.haml +33 -0
- data/site/src/about/performance.haml +31 -0
- data/site/src/about/workflows.haml +34 -0
- data/site/src/contribute/index.haml +65 -0
- data/site/src/contribute/style.haml +297 -0
- data/site/src/css/active4d.css +114 -0
- data/site/src/css/all_hallows_eve.css +72 -0
- data/site/src/css/all_themes.css +3299 -0
- data/site/src/css/amp.css +260 -0
- data/site/src/css/amy.css +147 -0
- data/site/src/css/blackboard.css +88 -0
- data/site/src/css/brilliance_black.css +605 -0
- data/site/src/css/brilliance_dull.css +599 -0
- data/site/src/css/cobalt.css +149 -0
- data/site/src/css/cur_amp.css +185 -0
- data/site/src/css/dawn.css +121 -0
- data/site/src/css/eiffel.css +121 -0
- data/site/src/css/espresso_libre.css +109 -0
- data/site/src/css/idle.css +62 -0
- data/site/src/css/iplastic.css +80 -0
- data/site/src/css/lazy.css +73 -0
- data/site/src/css/mac_classic.css +123 -0
- data/site/src/css/magicwb_amiga.css +104 -0
- data/site/src/css/pastels_on_dark.css +188 -0
- data/site/src/css/reset.css +55 -0
- data/site/src/css/slush_poppies.css +85 -0
- data/site/src/css/spacecadet.css +51 -0
- data/site/src/css/sunburst.css +180 -0
- data/site/src/css/twilight.css +137 -0
- data/site/src/css/zenburnesque.css +91 -0
- data/site/src/get/index.haml +32 -0
- data/site/src/helpers.rb +121 -0
- data/site/src/images/amp_logo.png +0 -0
- data/site/src/images/carbonica.png +0 -0
- data/site/src/images/revolution.png +0 -0
- data/site/src/images/tab-bg.png +0 -0
- data/site/src/images/tab-sliding-left.png +0 -0
- data/site/src/images/tab-sliding-right.png +0 -0
- data/site/src/include/_footer.haml +22 -0
- data/site/src/include/_header.haml +17 -0
- data/site/src/index.haml +104 -0
- data/site/src/learn/index.haml +46 -0
- data/site/src/scripts/jquery-1.3.2.min.js +19 -0
- data/site/src/scripts/jquery.cookie.js +96 -0
- data/tasks/stats.rake +155 -0
- data/tasks/yard.rake +171 -0
- data/test/dirstate_tests/dirstate +0 -0
- data/test/dirstate_tests/hgrc +5 -0
- data/test/dirstate_tests/test_dir_state.rb +192 -0
- data/test/functional_tests/resources/.hgignore +2 -0
- data/test/functional_tests/resources/STYLE.txt +25 -0
- data/test/functional_tests/resources/command.rb +372 -0
- data/test/functional_tests/resources/commands/annotate.rb +57 -0
- data/test/functional_tests/resources/commands/experimental/lolcats.rb +17 -0
- data/test/functional_tests/resources/commands/heads.rb +22 -0
- data/test/functional_tests/resources/commands/manifest.rb +12 -0
- data/test/functional_tests/resources/commands/status.rb +90 -0
- data/test/functional_tests/resources/version2/.hgignore +5 -0
- data/test/functional_tests/resources/version2/STYLE.txt +25 -0
- data/test/functional_tests/resources/version2/command.rb +372 -0
- data/test/functional_tests/resources/version2/commands/annotate.rb +45 -0
- data/test/functional_tests/resources/version2/commands/experimental/lolcats.rb +17 -0
- data/test/functional_tests/resources/version2/commands/heads.rb +22 -0
- data/test/functional_tests/resources/version2/commands/manifest.rb +12 -0
- data/test/functional_tests/resources/version2/commands/status.rb +90 -0
- data/test/functional_tests/resources/version3/.hgignore +5 -0
- data/test/functional_tests/resources/version3/STYLE.txt +31 -0
- data/test/functional_tests/resources/version3/command.rb +376 -0
- data/test/functional_tests/resources/version3/commands/annotate.rb +45 -0
- data/test/functional_tests/resources/version3/commands/experimental/lolcats.rb +17 -0
- data/test/functional_tests/resources/version3/commands/heads.rb +22 -0
- data/test/functional_tests/resources/version3/commands/manifest.rb +12 -0
- data/test/functional_tests/resources/version3/commands/status.rb +90 -0
- data/test/functional_tests/resources/version4/.hgignore +5 -0
- data/test/functional_tests/resources/version4/STYLE.txt +31 -0
- data/test/functional_tests/resources/version4/command.rb +376 -0
- data/test/functional_tests/resources/version4/commands/experimental/lolcats.rb +17 -0
- data/test/functional_tests/resources/version4/commands/heads.rb +22 -0
- data/test/functional_tests/resources/version4/commands/manifest.rb +12 -0
- data/test/functional_tests/resources/version4/commands/stats.rb +25 -0
- data/test/functional_tests/resources/version4/commands/status.rb +90 -0
- data/test/functional_tests/resources/version5_1/.hgignore +5 -0
- data/test/functional_tests/resources/version5_1/STYLE.txt +2 -0
- data/test/functional_tests/resources/version5_1/command.rb +374 -0
- data/test/functional_tests/resources/version5_1/commands/experimental/lolcats.rb +17 -0
- data/test/functional_tests/resources/version5_1/commands/heads.rb +22 -0
- data/test/functional_tests/resources/version5_1/commands/manifest.rb +12 -0
- data/test/functional_tests/resources/version5_1/commands/stats.rb +25 -0
- data/test/functional_tests/resources/version5_1/commands/status.rb +90 -0
- data/test/functional_tests/resources/version5_2/.hgignore +5 -0
- data/test/functional_tests/resources/version5_2/STYLE.txt +14 -0
- data/test/functional_tests/resources/version5_2/command.rb +376 -0
- data/test/functional_tests/resources/version5_2/commands/experimental/lolcats.rb +17 -0
- data/test/functional_tests/resources/version5_2/commands/manifest.rb +12 -0
- data/test/functional_tests/resources/version5_2/commands/newz.rb +12 -0
- data/test/functional_tests/resources/version5_2/commands/stats.rb +25 -0
- data/test/functional_tests/resources/version5_2/commands/status.rb +90 -0
- data/test/functional_tests/test_functional.rb +604 -0
- data/test/localrepo_tests/test_local_repo.rb +121 -0
- data/test/localrepo_tests/testrepo.tar.gz +0 -0
- data/test/manifest_tests/00manifest.i +0 -0
- data/test/manifest_tests/test_manifest.rb +72 -0
- data/test/merge_tests/base.txt +10 -0
- data/test/merge_tests/expected.local.txt +16 -0
- data/test/merge_tests/local.txt +11 -0
- data/test/merge_tests/remote.txt +11 -0
- data/test/merge_tests/test_merge.rb +26 -0
- data/test/revlog_tests/00changelog.i +0 -0
- data/test/revlog_tests/revision_added_changelog.i +0 -0
- data/test/revlog_tests/test_adding_index.i +0 -0
- data/test/revlog_tests/test_revlog.rb +333 -0
- data/test/revlog_tests/testindex.i +0 -0
- data/test/store_tests/store.tar.gz +0 -0
- data/test/store_tests/test_fncache_store.rb +122 -0
- data/test/test_amp.rb +9 -0
- data/test/test_base85.rb +14 -0
- data/test/test_bdiff.rb +42 -0
- data/test/test_commands.rb +122 -0
- data/test/test_difflib.rb +50 -0
- data/test/test_helper.rb +15 -0
- data/test/test_journal.rb +29 -0
- data/test/test_match.rb +134 -0
- data/test/test_mdiff.rb +74 -0
- data/test/test_mpatch.rb +14 -0
- data/test/test_support.rb +24 -0
- metadata +385 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
module Amp
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# == Match
|
|
5
|
+
# In this project, we came to a fork in the road: port the match class,
|
|
6
|
+
# 200+ lines of strange and convoluted Python, or write our own matcher.
|
|
7
|
+
# We chose to write our own matcher, and it was originally just a proc
|
|
8
|
+
# that would be passed around. After a few days of working with that,
|
|
9
|
+
# we then decided that it would be best to do our own implementation of
|
|
10
|
+
# their Match class, because we needed access to three things from this
|
|
11
|
+
# one object: the explicit files passed, the includes, and the excludes.
|
|
12
|
+
class Match
|
|
13
|
+
extend Ignore
|
|
14
|
+
|
|
15
|
+
attr_reader :block
|
|
16
|
+
attr_reader :files
|
|
17
|
+
attr_reader :include
|
|
18
|
+
attr_reader :exclude
|
|
19
|
+
|
|
20
|
+
##
|
|
21
|
+
# Very similar to #new -- the only difference is that instead of
|
|
22
|
+
# having to pass Regexps as :include or :exclude, you pass in
|
|
23
|
+
# strings, and the strings are parsed and converted into regexps.
|
|
24
|
+
# This is really the same as #initialize.
|
|
25
|
+
#
|
|
26
|
+
# @see new
|
|
27
|
+
# @param [Hash, [#include?, String, String]] either a hash or
|
|
28
|
+
# arrays in the order of: files, include, exclude
|
|
29
|
+
def self.create(*args, &block)
|
|
30
|
+
args = args.first
|
|
31
|
+
includer, excluder = regexp_for(args[:includer]), regexp_for(args[:excluder])
|
|
32
|
+
|
|
33
|
+
new :files => args[:files],
|
|
34
|
+
:include => includer,
|
|
35
|
+
:exclude => excluder, &block
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
##
|
|
39
|
+
# To remove code duplication. This will return a regexp given +arg+
|
|
40
|
+
# If arg is a string, it will turn it into a Regexp. If it's a Regexp,
|
|
41
|
+
# it returns +arg+.
|
|
42
|
+
#
|
|
43
|
+
# This is called from Match::create, so it needs to be a class method (duh)
|
|
44
|
+
#
|
|
45
|
+
# @param [Regexp, String] arg
|
|
46
|
+
# @return [Regexp]
|
|
47
|
+
def self.regexp_for(arg)
|
|
48
|
+
case arg
|
|
49
|
+
when Regexp
|
|
50
|
+
[arg]
|
|
51
|
+
when Array
|
|
52
|
+
matcher_for_text arg.join("\n") if arg.any?
|
|
53
|
+
when String
|
|
54
|
+
[matcher_for_string(arg)] if arg.any?
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
##
|
|
59
|
+
# +args+ can either be a hash (with a block supplied separately)
|
|
60
|
+
# or a list of arguments in the form of:
|
|
61
|
+
# files, includes, excludes, &block
|
|
62
|
+
#
|
|
63
|
+
# The block should be used for things that can't be represented as
|
|
64
|
+
# regular expressions. Thus, everything taken from the command line
|
|
65
|
+
# is presented as either an include or an exclude, because blocks
|
|
66
|
+
# are impossible from the console.
|
|
67
|
+
#
|
|
68
|
+
# @example
|
|
69
|
+
# Match.new :files => [] do |file|
|
|
70
|
+
# file =~ /test_(.+).rb$/
|
|
71
|
+
# end
|
|
72
|
+
# @example Match.new :include => /\.rbc$/
|
|
73
|
+
# @example Match.new([]) {|file| file =~ /test_(.+).rb$/ }
|
|
74
|
+
# @param [Hash, [#include?, Regexp, Regexp] either a hash or
|
|
75
|
+
# arrays in the order of: files, include, exclude
|
|
76
|
+
def initialize(*args, &block)
|
|
77
|
+
if (hash = args.first).is_a? Hash
|
|
78
|
+
@files = hash[:files] || []
|
|
79
|
+
@include = hash[:include]
|
|
80
|
+
@exclude = hash[:exclude]
|
|
81
|
+
|
|
82
|
+
else
|
|
83
|
+
files, include_, exclude, block = *args
|
|
84
|
+
|
|
85
|
+
@files = files || []
|
|
86
|
+
@include = include_
|
|
87
|
+
@exclude = exclude
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
@block = block || proc { false }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
##
|
|
94
|
+
# Is +file+ an exact match?
|
|
95
|
+
#
|
|
96
|
+
# @param [String] file the file to test
|
|
97
|
+
# @return [Boolean] is it an exact match?
|
|
98
|
+
def exact?(file)
|
|
99
|
+
@files.include?(file)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
##
|
|
103
|
+
# Is this +file+ being excluded? Does it automatically
|
|
104
|
+
# fail?
|
|
105
|
+
#
|
|
106
|
+
# @param [String] file the file to test
|
|
107
|
+
# @return [Boolean] is it a failure match?
|
|
108
|
+
def failure?(file)
|
|
109
|
+
@exclude && @exclude.any? {|r| file =~ r}
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
##
|
|
113
|
+
# Is it an exact match or an approximate match and not
|
|
114
|
+
# a file to be excluded?
|
|
115
|
+
#
|
|
116
|
+
# If a file is to be both included and excluded, all
|
|
117
|
+
# hell is let loose. You have been warned.
|
|
118
|
+
#
|
|
119
|
+
# @param [String] file the file to test
|
|
120
|
+
# @return [Boolean] does it pass?
|
|
121
|
+
def call(file)
|
|
122
|
+
if exact? file and failure? file
|
|
123
|
+
raise StandardError.new("File #{file.inspect} is to be both included and excluded")
|
|
124
|
+
end
|
|
125
|
+
# `and` because it's loosely binding
|
|
126
|
+
exact?(file) || included?(file) || approximate?(file) and !failure?(file)
|
|
127
|
+
end
|
|
128
|
+
alias_method :[], :call
|
|
129
|
+
|
|
130
|
+
##
|
|
131
|
+
# Is it to be included?
|
|
132
|
+
#
|
|
133
|
+
# @param [String] file the file to test
|
|
134
|
+
# @return [Boolean] is it to be included?
|
|
135
|
+
def included?(file)
|
|
136
|
+
@include && @include.any? {|r| file =~ r}
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
##
|
|
140
|
+
# Is it an approximate match?
|
|
141
|
+
#
|
|
142
|
+
# @param [String] file the file to test
|
|
143
|
+
# @return [Boolean] is it an approximate match?
|
|
144
|
+
def approximate?(file)
|
|
145
|
+
return false if exact? file
|
|
146
|
+
return false if (@include.nil? && @block.nil?)
|
|
147
|
+
included?(file) || (@block && @block.call(file))
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
module Amp
|
|
2
|
+
module Support
|
|
3
|
+
##
|
|
4
|
+
# = MultiIO
|
|
5
|
+
# A MultiIO is a class which joins multiple IO classes together. It responds to
|
|
6
|
+
# #read, and its constituent IOs must respond to #read. Since, currently, it only
|
|
7
|
+
# needs to be able to read (and perhaps rewind), that's all it does. It allows one
|
|
8
|
+
# to feed, say, 3 separate input IO objects into a GZipWriter, and have it seamlessly
|
|
9
|
+
# traverse all 3 IOs.
|
|
10
|
+
class MultiIO
|
|
11
|
+
# These are all the base IO objects we are joining together.
|
|
12
|
+
attr_accessor :ios
|
|
13
|
+
# Points to the current IO object in the @ios array.
|
|
14
|
+
attr_accessor :current_io_idx
|
|
15
|
+
# Tracks our current index into the "joined" stream. In other words, if they were
|
|
16
|
+
# all lumped into 1 stream, how many bytes in would we be?
|
|
17
|
+
attr_accessor :current_pos
|
|
18
|
+
|
|
19
|
+
##
|
|
20
|
+
# Initializes the MultiIO to contain the given IO objects, in the order in which
|
|
21
|
+
# they are specified as arguments.
|
|
22
|
+
#
|
|
23
|
+
# @param [Array<IO>] ios The IO objects we are concatenating
|
|
24
|
+
def initialize(*ios)
|
|
25
|
+
@ios = ios
|
|
26
|
+
rewind
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
##
|
|
30
|
+
# Rewinds all the IO objects to position 0.
|
|
31
|
+
def rewind
|
|
32
|
+
@ios.each {|io| io.seek(0) }
|
|
33
|
+
@current_pos = 0
|
|
34
|
+
@current_io_idx = 0
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# Gets the current position in the concatenated IO stream.
|
|
39
|
+
#
|
|
40
|
+
# @return [Integer] position in the IO stream (if all were 1 big stream, that is)
|
|
41
|
+
def tell; @current_pos; end
|
|
42
|
+
|
|
43
|
+
##
|
|
44
|
+
# Reads from the concatenated IO stream, crossing streams if necessary.
|
|
45
|
+
# (DON'T CROSS THE STREAMS!!!!)
|
|
46
|
+
#
|
|
47
|
+
# @param [Integer] amt (nil) The number of bytes to read from the overall stream.
|
|
48
|
+
# If +nil+, reads until the end of the stream.
|
|
49
|
+
# @return [String] the data read in from the stream
|
|
50
|
+
def read(amt=nil)
|
|
51
|
+
if amt==nil # if nil, read it all
|
|
52
|
+
return @ios[@current_io_idx..-1].map {|io| io.read}.join
|
|
53
|
+
end
|
|
54
|
+
results = [] # result strings
|
|
55
|
+
amount_read = 0 # how much have we read? We need this to match the +amt+ param
|
|
56
|
+
cur_spot = current_io.tell # our current position
|
|
57
|
+
while amount_read < amt # until we've read enough to meet the request
|
|
58
|
+
results << current_io.read(amt - amount_read) # read enough to finish
|
|
59
|
+
amount_read += current_io.tell - cur_spot # but we might not have actually read that much
|
|
60
|
+
@current_pos += current_io.tell - cur_spot # update ivar
|
|
61
|
+
# Do we need to go to the next IO stream?
|
|
62
|
+
if amount_read < amt && @current_io_idx < @ios.size - 1
|
|
63
|
+
# go to the next stream
|
|
64
|
+
@current_io_idx += 1
|
|
65
|
+
# reset it just in case
|
|
66
|
+
current_io.seek(0)
|
|
67
|
+
# are we at the last stream?
|
|
68
|
+
elsif @current_io_idx >= @ios.size - 1
|
|
69
|
+
break
|
|
70
|
+
end
|
|
71
|
+
# if we need to read from another stream, then remember we're at the start of it
|
|
72
|
+
cur_spot = 0
|
|
73
|
+
end
|
|
74
|
+
# join 'em up
|
|
75
|
+
results.join
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
##
|
|
80
|
+
# Returns the current IO object - we use it for reading
|
|
81
|
+
#
|
|
82
|
+
# @return [IO] the current IO object (that we should use if we need to read or seek)
|
|
83
|
+
def current_io; @ios[@current_io_idx]; end
|
|
84
|
+
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
module Amp
|
|
2
|
+
# opens files
|
|
3
|
+
class Opener
|
|
4
|
+
|
|
5
|
+
attr_reader :root
|
|
6
|
+
|
|
7
|
+
attr_accessor :create_mode
|
|
8
|
+
attr_accessor :default
|
|
9
|
+
|
|
10
|
+
alias_method :base, :root
|
|
11
|
+
|
|
12
|
+
##
|
|
13
|
+
# Creates a new opener with a root of +base+, and also set to open files in
|
|
14
|
+
# the .hg subdirectory. If you set .default = :open_file, it will no longer
|
|
15
|
+
# open files in the .hg subdir.
|
|
16
|
+
#
|
|
17
|
+
# @param [String] base the root directory of the repository this opener will be
|
|
18
|
+
# used on
|
|
19
|
+
def initialize(base)
|
|
20
|
+
@root = File.expand_path base
|
|
21
|
+
@create_mode = nil
|
|
22
|
+
@default = nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# Returns the path to the opener's root.
|
|
27
|
+
#
|
|
28
|
+
# @return path to the opener's root, as an absolute path.
|
|
29
|
+
def path
|
|
30
|
+
if @default == :open_file
|
|
31
|
+
"#{root}/"
|
|
32
|
+
else
|
|
33
|
+
"#{root}/.hg/"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# Read the file passed in with mode 'r'.
|
|
39
|
+
# Synonymous with File.open(+file+, 'r') {|f| f.read } and
|
|
40
|
+
# File.read(+file+)
|
|
41
|
+
#
|
|
42
|
+
# @param [String] file the relative path to the file we're opening
|
|
43
|
+
# @return [String] the contents of the file
|
|
44
|
+
def read(file)
|
|
45
|
+
res = nil
|
|
46
|
+
open(file, 'r') {|f| res = f.read }
|
|
47
|
+
res
|
|
48
|
+
end
|
|
49
|
+
alias_method :contents, :read
|
|
50
|
+
|
|
51
|
+
##
|
|
52
|
+
# Opens up the given file, exactly like you would do with File.open.
|
|
53
|
+
# The parameters are the same. Defaults to opening a file in the .hg/
|
|
54
|
+
# folder, but if @default == :open_file, will open it from the working
|
|
55
|
+
# directory.
|
|
56
|
+
#
|
|
57
|
+
# If the mode includes write privileges, then the write will use an
|
|
58
|
+
# atomic temporary file.
|
|
59
|
+
#
|
|
60
|
+
# @param [String] file the path to the file to open
|
|
61
|
+
# @param [String] mode the read/write mode to open with (standard
|
|
62
|
+
# C choices here)
|
|
63
|
+
# @yield Can yield the opened file if the block form is used
|
|
64
|
+
def open(file, mode='r', &block)
|
|
65
|
+
if @default == :open_file
|
|
66
|
+
open_file file, mode, &block
|
|
67
|
+
else
|
|
68
|
+
open_hg file, mode, &block
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def join(file)
|
|
73
|
+
File.join(root, file)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
##
|
|
77
|
+
# Opens a file in the .hg repository using +@root+. This method
|
|
78
|
+
# operates atomically, and ensures that the file is always closed
|
|
79
|
+
# after use. The temporary files (while being atomically written)
|
|
80
|
+
# are stored in "#{@root}/.hg", and are deleted after use. If only
|
|
81
|
+
# a read is being done, it instead uses Kernel::open instead of
|
|
82
|
+
# File::amp_atomic_write.
|
|
83
|
+
#
|
|
84
|
+
# @param [String] file the file to open
|
|
85
|
+
# @param [String] mode the mode with which to open the file ("w", "r", "a", ...)
|
|
86
|
+
# @yield [file] code to run on the file
|
|
87
|
+
# @yieldparam [File] file the opened file
|
|
88
|
+
def open_hg(file, mode='w', &block)
|
|
89
|
+
open_up_file File.join(root, ".hg"), file, mode, &block
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
##
|
|
93
|
+
# Opens a file in the repository (not in .hg).
|
|
94
|
+
# Writes are done atomically, and reads are efficiently
|
|
95
|
+
# done with Kernel::open. THIS IS NOT +open_up_file+!!!
|
|
96
|
+
#
|
|
97
|
+
# @param [String] file the file to open
|
|
98
|
+
# @param [String] mode the mode with which to open the file ("w", "r", "a", ...)
|
|
99
|
+
# @yield [file] code to run on the file
|
|
100
|
+
# @yieldparam [File] file the opened file
|
|
101
|
+
def open_file(file, mode='w', &block)
|
|
102
|
+
open_up_file root, file, mode, &block
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
##
|
|
106
|
+
# This does the actual opening of a file.
|
|
107
|
+
#
|
|
108
|
+
# @param [String] dir This dir is where the temp file is made, but ALSO
|
|
109
|
+
# the parent dir of +file+
|
|
110
|
+
# @param [String] file Just the file name. It must exist at "#{dir}/#{file}"
|
|
111
|
+
def open_up_file(dir, file, mode, &block)
|
|
112
|
+
path = File.join dir, file
|
|
113
|
+
if mode == 'r' # if we're doing a read, make this super snappy
|
|
114
|
+
Kernel::open path, mode, &block
|
|
115
|
+
else # we're doing a write
|
|
116
|
+
File::amp_atomic_write path, mode, @create_mode, dir, &block
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module Kernel
|
|
2
|
+
def ruby_19?; (RUBY_VERSION >= "1.9"); end
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
if RUBY_VERSION < "1.9"
|
|
6
|
+
class String
|
|
7
|
+
# DON'T USE String#each. Use String#each_line
|
|
8
|
+
def lines
|
|
9
|
+
self.split("\n").each do |l|
|
|
10
|
+
yield l
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
##
|
|
15
|
+
# Returns the numeric, ascii value of the first character
|
|
16
|
+
# in the string.
|
|
17
|
+
#
|
|
18
|
+
# @return [Fixnum] the ascii value of the first character in the string
|
|
19
|
+
def ord
|
|
20
|
+
self[0]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
class Object
|
|
24
|
+
def tap
|
|
25
|
+
yield self
|
|
26
|
+
self
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
else
|
|
30
|
+
# 1.9 +
|
|
31
|
+
# Autoload bug in 1.9 means we have to directly require these. FML.
|
|
32
|
+
require 'continuation'
|
|
33
|
+
require 'zlib'
|
|
34
|
+
require 'stringio'
|
|
35
|
+
require 'fileutils'
|
|
36
|
+
class String
|
|
37
|
+
# String doesn't include Enumerable in Ruby 1.9, so we lose #any?.
|
|
38
|
+
# Luckily it's quite easy to implement.
|
|
39
|
+
#
|
|
40
|
+
# @return [Boolean] does the string have anything in it?
|
|
41
|
+
def any?
|
|
42
|
+
size > 0
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class File
|
|
47
|
+
##
|
|
48
|
+
# This is in ftools in Ruby 1.8.x, but now it's in FileUtils. So
|
|
49
|
+
# this is essentially an alias to it. Silly ftools, trix are for kids.
|
|
50
|
+
def self.copy(*args)
|
|
51
|
+
FileUtils.copy(*args)
|
|
52
|
+
end
|
|
53
|
+
##
|
|
54
|
+
# This is in ftools in Ruby 1.8.x, but now it's in FileUtils. So
|
|
55
|
+
# this is essentially an alias to it. Silly ftools, trix are for kids.
|
|
56
|
+
def self.move(*args)
|
|
57
|
+
FileUtils.move(*args)
|
|
58
|
+
end
|
|
59
|
+
##
|
|
60
|
+
# This is in ftools in Ruby 1.8.x, but now it's in FileUtils. So
|
|
61
|
+
# this is essentially an alias to it. Silly ftools, trix are for kids.
|
|
62
|
+
def self.makedirs(*args)
|
|
63
|
+
FileUtils.makedirs(*args)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,1095 @@
|
|
|
1
|
+
|
|
2
|
+
require 'digest'
|
|
3
|
+
|
|
4
|
+
if RUBY_VERSION < "1.9"
|
|
5
|
+
require 'ftools'
|
|
6
|
+
|
|
7
|
+
autoload :Etc, 'etc'
|
|
8
|
+
autoload :Pathname, 'pathname'
|
|
9
|
+
autoload :Tempfile, 'tempfile'
|
|
10
|
+
autoload :Socket, 'socket'
|
|
11
|
+
autoload :WeakRef, 'weakref'
|
|
12
|
+
else
|
|
13
|
+
require 'fileutils'
|
|
14
|
+
require 'socket'
|
|
15
|
+
require 'pathname'
|
|
16
|
+
require 'etc'
|
|
17
|
+
require 'tempfile'
|
|
18
|
+
require 'weakref'
|
|
19
|
+
end
|
|
20
|
+
autoload :ERB, 'erb'
|
|
21
|
+
|
|
22
|
+
Boolean = :bool unless defined? Boolean
|
|
23
|
+
|
|
24
|
+
class OSError < StandardError; end
|
|
25
|
+
# _ ___
|
|
26
|
+
# /\) _ // 7
|
|
27
|
+
# _ / / (/\ (_,_/\
|
|
28
|
+
# /\) ( Y) \ \ \ \
|
|
29
|
+
# / / "" (Y ) \ \
|
|
30
|
+
# ( Y) _ "" _\ \__
|
|
31
|
+
# "" (/\ _ ( \ )
|
|
32
|
+
# \ \ /\) \___\___/
|
|
33
|
+
# (Y ) / /
|
|
34
|
+
# "" ( Y)
|
|
35
|
+
# "" This is the AbortError. Fear it.
|
|
36
|
+
# 4/20. nuff said (c'est la verite)
|
|
37
|
+
# Strange. These used to be ASCII penises.
|
|
38
|
+
class AbortError < StandardError
|
|
39
|
+
def to_s
|
|
40
|
+
"abort: "+super
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
module Kernel
|
|
45
|
+
def abort(str)
|
|
46
|
+
AbortError.new str
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class LockError < StandardError
|
|
51
|
+
attr_reader :errno, :filename, :desc
|
|
52
|
+
def initialize(errno, strerror, filename, desc)
|
|
53
|
+
super(strerror)
|
|
54
|
+
@errno, @filename, @desc = errno, filename, desc
|
|
55
|
+
end
|
|
56
|
+
def to_s
|
|
57
|
+
"LockError (#{@errno} @ #{@filename}) #{@strerror}: #{super}"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
class LockHeld < LockError
|
|
62
|
+
attr_reader :locker
|
|
63
|
+
def initialize(errno, filename, desc, locker)
|
|
64
|
+
super(errno, "Lock Held", filename, desc)
|
|
65
|
+
@locker = locker
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
class LockUnavailable < LockError; end
|
|
69
|
+
|
|
70
|
+
class AuthorizationError < StandardError; end
|
|
71
|
+
|
|
72
|
+
module Platform
|
|
73
|
+
|
|
74
|
+
if RUBY_PLATFORM =~ /darwin/i
|
|
75
|
+
OS = :unix
|
|
76
|
+
IMPL = :macosx
|
|
77
|
+
elsif RUBY_PLATFORM =~ /linux/i
|
|
78
|
+
OS = :unix
|
|
79
|
+
IMPL = :linux
|
|
80
|
+
elsif RUBY_PLATFORM =~ /freebsd/i
|
|
81
|
+
OS = :unix
|
|
82
|
+
IMPL = :freebsd
|
|
83
|
+
elsif RUBY_PLATFORM =~ /netbsd/i
|
|
84
|
+
OS = :unix
|
|
85
|
+
IMPL = :netbsd
|
|
86
|
+
elsif RUBY_PLATFORM =~ /mswin/i
|
|
87
|
+
OS = :win32
|
|
88
|
+
IMPL = :mswin
|
|
89
|
+
elsif RUBY_PLATFORM =~ /cygwin/i
|
|
90
|
+
OS = :unix
|
|
91
|
+
IMPL = :cygwin
|
|
92
|
+
elsif RUBY_PLATFORM =~ /mingw/i
|
|
93
|
+
OS = :win32
|
|
94
|
+
IMPL = :mingw
|
|
95
|
+
elsif RUBY_PLATFORM =~ /bccwin/i
|
|
96
|
+
OS = :win32
|
|
97
|
+
IMPL = :bccwin
|
|
98
|
+
elsif RUBY_PLATFORM =~ /wince/i
|
|
99
|
+
OS = :win32
|
|
100
|
+
IMPL = :wince
|
|
101
|
+
elsif RUBY_PLATFORM =~ /vms/i
|
|
102
|
+
OS = :vms
|
|
103
|
+
IMPL = :vms
|
|
104
|
+
elsif RUBY_PLATFORM =~ /os2/i
|
|
105
|
+
OS = :os2
|
|
106
|
+
IMPL = :os2 # maybe there is some better choice here?
|
|
107
|
+
else
|
|
108
|
+
OS = :unknown
|
|
109
|
+
IMPL = :unknown
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
if RUBY_PLATFORM =~ /(i\d86)/i
|
|
113
|
+
ARCH = :x86
|
|
114
|
+
elsif RUBY_PLATFORM =~ /ia64/i
|
|
115
|
+
ARCH = :ia64
|
|
116
|
+
elsif RUBY_PLATFORM =~ /powerpc/i
|
|
117
|
+
ARCH = :powerpc
|
|
118
|
+
elsif RUBY_PLATFORM =~ /alpha/i
|
|
119
|
+
ARCH = :alpha
|
|
120
|
+
elsif RUBY_PLATFORM =~ /universal/i
|
|
121
|
+
ARCH = :universal
|
|
122
|
+
else
|
|
123
|
+
ARCH = :unknown
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class File::Stat
|
|
130
|
+
|
|
131
|
+
##
|
|
132
|
+
# Used for comparing two files (approximately). This was
|
|
133
|
+
# our guide: http://docs.python.org/library/os.html#os.stat
|
|
134
|
+
#
|
|
135
|
+
# @param [File::Stat] other the other stats to compare
|
|
136
|
+
# @return [Boolean] whether they are similar enough or not
|
|
137
|
+
def ===(other)
|
|
138
|
+
self.mode == other.mode &&
|
|
139
|
+
self.ino == other.ino &&
|
|
140
|
+
self.dev == other.dev &&
|
|
141
|
+
self.nlink == other.nlink &&
|
|
142
|
+
self.uid == other.uid &&
|
|
143
|
+
self.gid == other.gid &&
|
|
144
|
+
self.size == other.size &&
|
|
145
|
+
self.atime == other.atime &&
|
|
146
|
+
self.mtime == other.atime &&
|
|
147
|
+
self.ctime == other.ctime
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
class Module
|
|
152
|
+
##
|
|
153
|
+
# Makes an instance or module method memoized. Works by aliasing
|
|
154
|
+
# the old method and creating a new one in its place.
|
|
155
|
+
#
|
|
156
|
+
# @param [Symbol, #to_sym] meth_name the name of the method to memoize
|
|
157
|
+
# @param [Boolean] module_function_please should we call module_function on
|
|
158
|
+
# the aliased method? necessary if you are memoizing a module's function
|
|
159
|
+
# made available as a singleton method via +module_function+.
|
|
160
|
+
# @return the module itself.
|
|
161
|
+
def memoize_method(meth_name, module_function_please = false)
|
|
162
|
+
meth_name = meth_name.to_sym
|
|
163
|
+
aliased_meth = "__memo_#{meth_name}".to_sym
|
|
164
|
+
# alias to a new method
|
|
165
|
+
alias_method aliased_meth, meth_name
|
|
166
|
+
# module_function the newly aliased method if necessary
|
|
167
|
+
if module_function_please && self.class == Module
|
|
168
|
+
module_function aliased_meth
|
|
169
|
+
end
|
|
170
|
+
# incase it doesn't exist yet
|
|
171
|
+
@__memo_cache ||= {}
|
|
172
|
+
# our new method! Replacing the old one.
|
|
173
|
+
define_method meth_name do |*args|
|
|
174
|
+
# we store the memoized data with an i-var.
|
|
175
|
+
@__memo_cache[meth_name] ||= {}
|
|
176
|
+
cache = @__memo_cache[meth_name]
|
|
177
|
+
|
|
178
|
+
# if we have the cached value, return it
|
|
179
|
+
result = cache[args]
|
|
180
|
+
return result if result
|
|
181
|
+
# cache miss. find the value
|
|
182
|
+
result = send(aliased_meth, *args)
|
|
183
|
+
cache[args] = result
|
|
184
|
+
result
|
|
185
|
+
end
|
|
186
|
+
self
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
module Kernel
|
|
191
|
+
##
|
|
192
|
+
# Allows any code called within the block to access non-existent files
|
|
193
|
+
# without raising an exception. Only "file not found" exceptions are
|
|
194
|
+
# ignored - all other exceptions will be raised as normal.
|
|
195
|
+
#
|
|
196
|
+
# @yield The block is run with all missing-file exceptions caught and ignored.
|
|
197
|
+
def ignore_missing_files
|
|
198
|
+
begin
|
|
199
|
+
yield
|
|
200
|
+
rescue Errno::ENOENT
|
|
201
|
+
rescue StandardError
|
|
202
|
+
raise
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
##
|
|
207
|
+
# The built-in Ruby 1.8.x implementation will only show a certain number
|
|
208
|
+
# of context lines at the start and end of its backtrace when an exception
|
|
209
|
+
# is raised. All other levels of the stack will be labeled "... 15 levels ..."
|
|
210
|
+
# Sadly, sometimes some important information is in those 15 levels, and without
|
|
211
|
+
# patching the interpreter, there's no way to just disable that abbreviation.
|
|
212
|
+
#
|
|
213
|
+
# So, we simply catch all exceptions, print their full backtrace, and then exit!
|
|
214
|
+
#
|
|
215
|
+
# @yield The block is run, and any exceptions raised print their full backtrace.
|
|
216
|
+
def full_backtrace_please
|
|
217
|
+
message = ["***** Left engine failure *****",
|
|
218
|
+
"***** Ejection system error *****",
|
|
219
|
+
"***** Vaccuum in booster engine *****"
|
|
220
|
+
][rand(3)]
|
|
221
|
+
begin
|
|
222
|
+
yield
|
|
223
|
+
rescue AbortError => e
|
|
224
|
+
Amp::UI.say "Operation aborted."
|
|
225
|
+
raise
|
|
226
|
+
rescue StandardError => e
|
|
227
|
+
Amp::UI.say message
|
|
228
|
+
Amp::UI.say e.to_s
|
|
229
|
+
e.backtrace.each {|err| Amp::UI.say "\tfrom #{err}" }
|
|
230
|
+
exit
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
class Dir
|
|
237
|
+
|
|
238
|
+
##
|
|
239
|
+
# Iterates over a directory, yielding an array with the
|
|
240
|
+
# {File::Stat} entry for each file/directory in the requested directory.
|
|
241
|
+
# @param [String] path the path to iterate over
|
|
242
|
+
# @param [Boolean] stat should we retrieve stat information?
|
|
243
|
+
# @param [String] skip a filename to always skip
|
|
244
|
+
# @return [[String, File::Stat, String]] Each entry in the format [File path,
|
|
245
|
+
# statistic struct, file type].
|
|
246
|
+
def self.stat_list path, stat=false, skip=nil
|
|
247
|
+
result = []
|
|
248
|
+
prefix = path
|
|
249
|
+
prefix += File::SEPARATOR unless prefix =~ /#{File::SEPARATOR}$/
|
|
250
|
+
names = Dir.entries(path).select {|i| i != "." && i != ".."}.sort
|
|
251
|
+
names.each do |fn|
|
|
252
|
+
st = File.lstat(prefix + fn)
|
|
253
|
+
return [] if fn == skip && File.directory?(prefix + fn)
|
|
254
|
+
if st.ftype && st.ftype !~ /unknown/
|
|
255
|
+
newval = [fn, st.ftype, st]
|
|
256
|
+
else
|
|
257
|
+
newval = [fn, st.ftype]
|
|
258
|
+
end
|
|
259
|
+
result << newval
|
|
260
|
+
yield newval if block_given?
|
|
261
|
+
end
|
|
262
|
+
result
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def self.tmpdir
|
|
266
|
+
"/tmp" # default, but it should never ever be used!
|
|
267
|
+
# i mean it's ok if it is
|
|
268
|
+
# but i'd be caught off guard if this ends up being used in the code
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
##
|
|
272
|
+
# Same as File.dirname, but returns an empty string instead of '.'
|
|
273
|
+
#
|
|
274
|
+
# @param [String] path the path to get the directory of
|
|
275
|
+
def self.dirname(path)
|
|
276
|
+
File.dirname(path) == '.' ? '' : File.dirname(path)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
class File
|
|
282
|
+
|
|
283
|
+
##
|
|
284
|
+
# Checks if a file exists, without following symlinks.
|
|
285
|
+
#
|
|
286
|
+
# @param [String] filename the path to the file to check
|
|
287
|
+
# @return [Boolean] whether or not the file exists (ignoring symlinks)
|
|
288
|
+
def amp_lexist?(filename)
|
|
289
|
+
!!File.lstat(filename) rescue false
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
##
|
|
293
|
+
# Sets a file's executable bit.
|
|
294
|
+
#
|
|
295
|
+
# @todo Windows version
|
|
296
|
+
# @param [String] path the path to the file
|
|
297
|
+
# @param [Boolean] executable sets whether the file is executable or not
|
|
298
|
+
def self.amp_set_executable(path, executable)
|
|
299
|
+
s = File.lstat(path).mode
|
|
300
|
+
sx = s & 0100
|
|
301
|
+
if executable && !sx
|
|
302
|
+
# Turn on +x for every +r bit when making a file executable
|
|
303
|
+
# and obey umask. (direct from merc. source)
|
|
304
|
+
File.chmod(s | (s & 0444) >> 2 & ~(File.umask(0)), path)
|
|
305
|
+
elsif !executable && sx
|
|
306
|
+
File.chmod(s & 0666 , path)
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
##
|
|
311
|
+
# Does a registry lookup.
|
|
312
|
+
# *nix version.
|
|
313
|
+
#
|
|
314
|
+
# @todo Add Windows Version
|
|
315
|
+
def self.amp_lookup_reg(a,b)
|
|
316
|
+
nil
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
##
|
|
320
|
+
# Finds an executable for {command}. Searches like the OS does. If
|
|
321
|
+
# command is a basename then PATH is searched for {command}. PATH
|
|
322
|
+
# isn't searched if command is an absolute or relative path.
|
|
323
|
+
# If command isn't found, nil is returned. *nix only.
|
|
324
|
+
#
|
|
325
|
+
# @todo Add Windows Version.
|
|
326
|
+
# @param [String] command the executable to find
|
|
327
|
+
# @return [String, nil] If the executable is found, the full path is returned.
|
|
328
|
+
def self.amp_find_executable(command)
|
|
329
|
+
find_if_exists = proc do |executable|
|
|
330
|
+
return executable if File.exist? executable
|
|
331
|
+
return nil
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
return find_if_exists[command] if command.include?(File::SEPARATOR)
|
|
335
|
+
ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
|
|
336
|
+
executable = find_if_exists[File.join(path, command)]
|
|
337
|
+
return executable if executable
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
nil
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
##
|
|
344
|
+
# taken from Rails' ActiveSupport
|
|
345
|
+
# all or nothing babyyyyyyyy
|
|
346
|
+
# use this only for writes, otherwise it's just inefficient
|
|
347
|
+
# file_name is FULL PATH
|
|
348
|
+
def self.amp_atomic_write(file_name, mode='w', default_mode=nil, temp_dir=Dir.tmpdir, &block)
|
|
349
|
+
File.makedirs(File.dirname(file_name))
|
|
350
|
+
FileUtils.touch(file_name) unless File.exists? file_name
|
|
351
|
+
# this is sorta like "checking out" a file
|
|
352
|
+
# but only if we're *just* writing
|
|
353
|
+
new_path = join temp_dir, amp_make_tmpname(basename(file_name))
|
|
354
|
+
unless mode == 'w'
|
|
355
|
+
copy(file_name, new_path) # allowing us to use mode "a" and others
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
# open and close it
|
|
360
|
+
val = Kernel::open new_path, mode, &block
|
|
361
|
+
|
|
362
|
+
begin
|
|
363
|
+
# Get original file permissions
|
|
364
|
+
old_stat = stat(file_name)
|
|
365
|
+
rescue Errno::ENOENT
|
|
366
|
+
# No old permissions, write a temp file to determine the defaults
|
|
367
|
+
check_name = ".permissions_check.#{Thread.current.object_id}.#{Process.pid}.#{rand(1000000)}"
|
|
368
|
+
Kernel::open(check_name, "w") { }
|
|
369
|
+
old_stat = stat(check_name)
|
|
370
|
+
unlink(check_name)
|
|
371
|
+
delete(check_name)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# do a chmod, pretty much
|
|
375
|
+
begin
|
|
376
|
+
nlink = File.amp_num_hardlinks(file_name)
|
|
377
|
+
rescue Errno::ENOENT, OSError
|
|
378
|
+
nlink = 0
|
|
379
|
+
d = File.dirname(file_name)
|
|
380
|
+
File.mkdir_p(d, default_mode) unless File.directory? d
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
new_mode = default_mode & 0666 if default_mode
|
|
384
|
+
|
|
385
|
+
# Overwrite original file with temp file
|
|
386
|
+
amp_force_rename(new_path, file_name)
|
|
387
|
+
|
|
388
|
+
# Set correct permissions on new file
|
|
389
|
+
chown(old_stat.uid, old_stat.gid, file_name)
|
|
390
|
+
chmod(new_mode || old_stat.mode, file_name)
|
|
391
|
+
|
|
392
|
+
val
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
##
|
|
396
|
+
# Makes a fancy, quite-random name for a temporary file.
|
|
397
|
+
# Uses the file's name, the current time, the process number, a random number,
|
|
398
|
+
# and the file's extension to make a very random filename.
|
|
399
|
+
#
|
|
400
|
+
# Of course, it could still fail.
|
|
401
|
+
#
|
|
402
|
+
# @param [String] basename The base name of the file - just the file's name and extension
|
|
403
|
+
# @return [String] the pseudo-random name of the file to be created
|
|
404
|
+
def self.amp_make_tmpname(basename)
|
|
405
|
+
case basename
|
|
406
|
+
when Array
|
|
407
|
+
prefix, suffix = *basename
|
|
408
|
+
else
|
|
409
|
+
prefix, suffix = basename, "."+File.extname(basename)
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
t = Time.now.strftime("%Y%m%d")
|
|
413
|
+
path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}-#{suffix}"
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
##
|
|
417
|
+
# Reads a range from the file.
|
|
418
|
+
#
|
|
419
|
+
# @param [Range] range the byte indices to read between (and including)
|
|
420
|
+
# @return [String] the data read from the file
|
|
421
|
+
def [](range)
|
|
422
|
+
p = pos
|
|
423
|
+
seek(range.first)
|
|
424
|
+
val = read(range.last - range.first + 1)
|
|
425
|
+
seek p
|
|
426
|
+
val
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
##
|
|
430
|
+
# Reads +n+ bytes at a time and yield them from the given file
|
|
431
|
+
#
|
|
432
|
+
# @param [Integer] num_bytes the number of bytes to yield
|
|
433
|
+
# @yield Yields a chunk that is at most +num_bytes+ from the file until the
|
|
434
|
+
# file is exhausted. Poor file, it's so tired.
|
|
435
|
+
# @yieldparam [String] the chunk from the file.
|
|
436
|
+
def amp_each_chunk(num_bytes = 4.kb)
|
|
437
|
+
buffer = nil
|
|
438
|
+
while buffer = read(num_bytes)
|
|
439
|
+
yield buffer
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
##
|
|
444
|
+
# Finds the number of hard links to the file.
|
|
445
|
+
#
|
|
446
|
+
# @param [String] file the full path to the file to lookup
|
|
447
|
+
# @return [Integer] the number of hard links to the file
|
|
448
|
+
def self.amp_num_hardlinks(file)
|
|
449
|
+
lstat = File.lstat(file)
|
|
450
|
+
raise OSError.new("no lstat on windows") if lstat.nil?
|
|
451
|
+
lstat.nlink
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
##
|
|
455
|
+
# All directories leading up to this path
|
|
456
|
+
#
|
|
457
|
+
# @example directories_to "/Users/ari/src/monkey.txt" # =>
|
|
458
|
+
# ["/Users/ari/src", "/Users/ari", "/Users"]
|
|
459
|
+
# @example directories_to "/Users/ari/src/monkey.txt", true # =>
|
|
460
|
+
# ["/Users/ari/src", "/Users/ari", "/Users", ""]
|
|
461
|
+
# @param [String] path the path to the file we're examining
|
|
462
|
+
# @param [Boolean] empty whether or not to return an empty string as well
|
|
463
|
+
# @return [Array] the directories leading up to this path
|
|
464
|
+
def self.amp_directories_to(path, empty=false)
|
|
465
|
+
dirs = path.split('/')[0..-2]
|
|
466
|
+
ret = []
|
|
467
|
+
|
|
468
|
+
dirs.size.times { ret << dirs.join('/'); dirs.pop }
|
|
469
|
+
ret << '' if empty
|
|
470
|
+
ret
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
##
|
|
474
|
+
# Forces a rename from file to dst, removing the dst file if it
|
|
475
|
+
# already exists. Avoids system exceptions that might result.
|
|
476
|
+
#
|
|
477
|
+
# @param [String] file the source file path
|
|
478
|
+
# @param [String] dst the destination file path
|
|
479
|
+
def self.amp_force_rename(file, dst)
|
|
480
|
+
return unless File.exist? file
|
|
481
|
+
if File.exist? dst
|
|
482
|
+
File.unlink dst
|
|
483
|
+
File.rename file, dst
|
|
484
|
+
else
|
|
485
|
+
File.rename file, dst
|
|
486
|
+
end
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
##
|
|
490
|
+
# Returns the full name of the file, excluding path information.
|
|
491
|
+
#
|
|
492
|
+
# @param [File] file the {File} to check
|
|
493
|
+
# @return the name of the file
|
|
494
|
+
def self.amp_name(file)
|
|
495
|
+
File.split(file.path).last
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
##
|
|
499
|
+
# Splits the path into two parts: pre-extension, and extension, including
|
|
500
|
+
# the dot.
|
|
501
|
+
# File.amp_split_extension "/usr/bin/conf.ini" => ["conf",".ini"]
|
|
502
|
+
#
|
|
503
|
+
# @param [String] path the path to the file to split up
|
|
504
|
+
# @return [String, String] the [filename pre extension, file extension] of
|
|
505
|
+
# the file provided.
|
|
506
|
+
def self.amp_split_extension(path)
|
|
507
|
+
ext = File.extname path
|
|
508
|
+
base = File.basename path, ext
|
|
509
|
+
[base, ext]
|
|
510
|
+
end
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
class Range
|
|
514
|
+
# Given two ranges return the range where they intersect or None.
|
|
515
|
+
#
|
|
516
|
+
# >>> intersect((0, 10), (0, 6))
|
|
517
|
+
# (0, 6)
|
|
518
|
+
# >>> intersect((0, 10), (5, 15))
|
|
519
|
+
# (5, 10)
|
|
520
|
+
# >>> intersect((0, 10), (10, 15))
|
|
521
|
+
# >>> intersect((0, 9), (10, 15))
|
|
522
|
+
# >>> intersect((0, 9), (7, 15))
|
|
523
|
+
# (7, 9)
|
|
524
|
+
def intersect(rb)
|
|
525
|
+
ra = self
|
|
526
|
+
start_a = [ra.begin, rb.begin].max
|
|
527
|
+
start_b = [ra.end, rb.end ].min
|
|
528
|
+
if start_a < start_b
|
|
529
|
+
start_a..start_b
|
|
530
|
+
else
|
|
531
|
+
nil
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
alias_method :-, :intersect
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
class Hash
|
|
538
|
+
|
|
539
|
+
##
|
|
540
|
+
# Given a list of key names, and a specified value, we create a hash
|
|
541
|
+
# with those keys all equal to +value+. Useful for making true/false
|
|
542
|
+
# tables with speedy lookup.
|
|
543
|
+
#
|
|
544
|
+
# @param [Enumerable] iterable any object with Enumerable mixed in can
|
|
545
|
+
# create a hash.
|
|
546
|
+
# @param [Object] value (true) the value to assign each key to in the resultant hash
|
|
547
|
+
# @return [Hash] a hash with keys from +iterable+, all set to +value+
|
|
548
|
+
def self.with_keys(iterable, value=true)
|
|
549
|
+
iterable.inject({}) {|h, k| h.merge!(k => value) }
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
##
|
|
553
|
+
# Create a subset of +self+ with keys +keys+.
|
|
554
|
+
def pick(*keys)
|
|
555
|
+
keys.inject({}) {|h, (k, v)| h[k] = v }
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
class Array
|
|
561
|
+
|
|
562
|
+
##
|
|
563
|
+
# Sums all the items in the array
|
|
564
|
+
#
|
|
565
|
+
# @return [Array] the items summed
|
|
566
|
+
def sum
|
|
567
|
+
inject(0) {|sum, x| sum + x }
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
##
|
|
571
|
+
# Returns the second item in the array
|
|
572
|
+
#
|
|
573
|
+
# @return [Object] the second item in the array
|
|
574
|
+
def second; self[1]; end
|
|
575
|
+
|
|
576
|
+
# Deletes the given range from the array, in-place.
|
|
577
|
+
def delete_range(range)
|
|
578
|
+
newend = (range.end < 0) ? self.size + range.end : range.end
|
|
579
|
+
newbegin = (range.begin < 0) ? self.size + range.begin : range.begin
|
|
580
|
+
newrange = Range.new newbegin, newend
|
|
581
|
+
pos = newrange.first
|
|
582
|
+
newrange.each {|i| self.delete_at pos }
|
|
583
|
+
|
|
584
|
+
self
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
def to_hash
|
|
588
|
+
inject({}) {|h, (k, v)| h.merge k => v }
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
def short_hex
|
|
592
|
+
map {|e| e.short_hex }
|
|
593
|
+
end
|
|
594
|
+
alias_method :short, :short_hex
|
|
595
|
+
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
class Integer
|
|
599
|
+
|
|
600
|
+
# methods for converting between file sizes
|
|
601
|
+
def bytes
|
|
602
|
+
self
|
|
603
|
+
end
|
|
604
|
+
alias_method :byte, :bytes
|
|
605
|
+
alias_method :b, :bytes
|
|
606
|
+
|
|
607
|
+
# methods for converting between file sizes
|
|
608
|
+
def kilobytes
|
|
609
|
+
1024 * bytes
|
|
610
|
+
end
|
|
611
|
+
alias_method :kilobyte, :kilobytes
|
|
612
|
+
alias_method :kb, :kilobytes
|
|
613
|
+
|
|
614
|
+
# methods for converting between file sizes
|
|
615
|
+
def megabytes
|
|
616
|
+
1024 * kilobytes
|
|
617
|
+
end
|
|
618
|
+
alias_method :megabyte, :megabytes
|
|
619
|
+
alias_method :mb, :megabytes
|
|
620
|
+
|
|
621
|
+
# methods for converting between file sizes
|
|
622
|
+
def gigabytes
|
|
623
|
+
1024 * megabytes
|
|
624
|
+
end
|
|
625
|
+
alias_method :gigabyte, :gigabytes
|
|
626
|
+
alias_method :gb, :gigabytes
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
##
|
|
630
|
+
# Forces this integer to be negative if it's supposed to be!
|
|
631
|
+
#
|
|
632
|
+
# @param [Fixnum] bits the number of bits to use - signed shorts are different from
|
|
633
|
+
# signed longs!
|
|
634
|
+
def to_signed(bits)
|
|
635
|
+
return to_signed_16 if bits == 16
|
|
636
|
+
return to_signed_32 if bits == 32
|
|
637
|
+
raise "Unexpected number of bits: #{bits}"
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
class String
|
|
643
|
+
##
|
|
644
|
+
# Returns the string, encoded for a tty terminal with the given color code.
|
|
645
|
+
#
|
|
646
|
+
# @param [String] color_code a TTY color code
|
|
647
|
+
# @return [String] the string wrapped in non-printing characters to make the text
|
|
648
|
+
# appear in a given color
|
|
649
|
+
def colorize(color_code)
|
|
650
|
+
"#{color_code}#{self}\e[0m"
|
|
651
|
+
end
|
|
652
|
+
|
|
653
|
+
# Returns the string, colored red.
|
|
654
|
+
def red; colorize("\e[31m"); end
|
|
655
|
+
def green; colorize("\e[32m"); end
|
|
656
|
+
def yellow; colorize("\e[33m"); end
|
|
657
|
+
def blue; colorize("\e[34m"); end
|
|
658
|
+
def magenta; colorize("\e[35m"); end
|
|
659
|
+
def cyan; colorize("\e[36m"); end
|
|
660
|
+
def white; colorize("\e[37m"); end
|
|
661
|
+
|
|
662
|
+
##
|
|
663
|
+
# Returns the path from +root+ to the path represented by the string. Will fail
|
|
664
|
+
# if the string is not inside +root+.
|
|
665
|
+
#
|
|
666
|
+
# @param [String] root the root from which we want the relative path
|
|
667
|
+
# @return [String] the relative path from +root+ to the string itself
|
|
668
|
+
def relative_path(root)
|
|
669
|
+
return '' if self == root
|
|
670
|
+
|
|
671
|
+
# return a more local path if possible...
|
|
672
|
+
return self[root.length..-1] if start_with? root
|
|
673
|
+
self # else we're outside the repo
|
|
674
|
+
end
|
|
675
|
+
|
|
676
|
+
# Am I equal to the NULL_ID used in revision logs?
|
|
677
|
+
def null?
|
|
678
|
+
self == Amp::RevlogSupport::Node::NULL_ID
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
# Am I not equal to the NULL_ID used in revision logs?
|
|
682
|
+
def not_null?
|
|
683
|
+
!(null?)
|
|
684
|
+
end
|
|
685
|
+
|
|
686
|
+
##
|
|
687
|
+
# Does the string start with the given prefix?
|
|
688
|
+
#
|
|
689
|
+
# @param [String] prefix the prefix to test
|
|
690
|
+
# @return [Boolean] does the string start with the given prefix?
|
|
691
|
+
def start_with?(prefix)
|
|
692
|
+
self[0,prefix.size] == prefix # self =~ /^#{str}/
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
##
|
|
696
|
+
# Does the string end with the given suffix?
|
|
697
|
+
#
|
|
698
|
+
# @param [String] suffix the suffix to test
|
|
699
|
+
# @return [Boolean] does the string end with the given suffix?
|
|
700
|
+
def end_with?(suffix)
|
|
701
|
+
self[-suffix.size, suffix.size] == suffix # self =~ /#{str}$/
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
##
|
|
705
|
+
# Pops the given character off the front of the string, but only if
|
|
706
|
+
# the string starts with the given character. Otherwise, nothing happens.
|
|
707
|
+
# Often used to remove troublesome leading slashes. Much like an "lchomp" method.
|
|
708
|
+
#
|
|
709
|
+
# @param [String] char the character to remove from the front of the string
|
|
710
|
+
# @return [String] the string with the leading +char+ removed (if it is there).
|
|
711
|
+
def shift(char)
|
|
712
|
+
return '' if self.empty?
|
|
713
|
+
return self[1..-1] if self.start_with? char
|
|
714
|
+
self
|
|
715
|
+
end
|
|
716
|
+
alias_method :lchomp, :shift
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
##
|
|
720
|
+
# Splits on newlines only, removing extra blank line at end if there is one.
|
|
721
|
+
# This is how mercurial does it and i'm sticking to it. This method is evil.
|
|
722
|
+
# DON'T USE IT.
|
|
723
|
+
def split_newlines(add_newlines=true)
|
|
724
|
+
return [] if self.empty?
|
|
725
|
+
lines = self.split("\n").map {|l| l + (add_newlines ? "\n" : "") }
|
|
726
|
+
return lines if lines.size == 1
|
|
727
|
+
if (add_newlines && lines.last == "\n") || (!add_newlines && lines.last.empty?)
|
|
728
|
+
lines.pop
|
|
729
|
+
else
|
|
730
|
+
lines[-1] = lines[-1][0..-2] if lines[-1][-1,1] == "\n"
|
|
731
|
+
end
|
|
732
|
+
lines
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
##
|
|
736
|
+
# Newer version of split_newlines that works better. This splits on newlines,
|
|
737
|
+
# but includes the newline in each entry in the resultant string array.
|
|
738
|
+
#
|
|
739
|
+
# @return [Array<String>] the string split up into lines
|
|
740
|
+
def split_lines_better
|
|
741
|
+
result = []
|
|
742
|
+
each_line {|l| result << l}
|
|
743
|
+
result
|
|
744
|
+
end
|
|
745
|
+
|
|
746
|
+
##
|
|
747
|
+
# easy md5!
|
|
748
|
+
#
|
|
749
|
+
# @return [Digest::MD5] the MD5 digest of the string in hex form
|
|
750
|
+
def md5
|
|
751
|
+
Digest::MD5.new.update(self)
|
|
752
|
+
end
|
|
753
|
+
|
|
754
|
+
##
|
|
755
|
+
# easy sha1!
|
|
756
|
+
# This is unsafe, as SHA1 kinda sucks.
|
|
757
|
+
#
|
|
758
|
+
# @return [Digest::SHA1] the SHA1 digest of the string in hex form
|
|
759
|
+
def sha1
|
|
760
|
+
Digest::SHA1.new.update(self)
|
|
761
|
+
end
|
|
762
|
+
|
|
763
|
+
##
|
|
764
|
+
# If the string is the name of a command, run it. Else,
|
|
765
|
+
# raise hell.
|
|
766
|
+
#
|
|
767
|
+
# @param [Hash] options hash of the options for the command
|
|
768
|
+
# @param [Array] args array of extra args
|
|
769
|
+
# @return [Amp::Command] the command which will be run
|
|
770
|
+
def run(options={}, args=[])
|
|
771
|
+
if cmd = Amp::Command[self]
|
|
772
|
+
cmd.run options, args
|
|
773
|
+
else
|
|
774
|
+
raise "No such command #{self}"
|
|
775
|
+
end
|
|
776
|
+
end
|
|
777
|
+
|
|
778
|
+
# Converts this text into hex. each letter is replaced with
|
|
779
|
+
# it's hex counterpart
|
|
780
|
+
def hexlify
|
|
781
|
+
str = ""
|
|
782
|
+
self.each_byte do |i|
|
|
783
|
+
str << i.to_s(16).rjust(2, "0")
|
|
784
|
+
end
|
|
785
|
+
str
|
|
786
|
+
end
|
|
787
|
+
|
|
788
|
+
##
|
|
789
|
+
# Converts this text into hex, and trims it a little for readability.
|
|
790
|
+
def short_hex
|
|
791
|
+
hexlify[0..9]
|
|
792
|
+
end
|
|
793
|
+
|
|
794
|
+
##
|
|
795
|
+
# removes the password from a url. else, just returns self
|
|
796
|
+
# @return [String] the URL with passwords censored.
|
|
797
|
+
def hide_password
|
|
798
|
+
if s = self.match(/^http(?:s)?:\/\/[^:]+(?::([^:]+))?(@)/)
|
|
799
|
+
string = ''
|
|
800
|
+
string << self[0..s.begin(1)-1] # get from beginning to the pass
|
|
801
|
+
string << '***'
|
|
802
|
+
string << self[s.begin(2)..-1]
|
|
803
|
+
string
|
|
804
|
+
else
|
|
805
|
+
self
|
|
806
|
+
end
|
|
807
|
+
end
|
|
808
|
+
|
|
809
|
+
##
|
|
810
|
+
# Adds minimal slashes to escape the string
|
|
811
|
+
# @return [String] the string slightly escaped.
|
|
812
|
+
def add_slashes
|
|
813
|
+
self.gsub(/\\/,"\\\\").gsub(/\n/,"\\n").gsub(/\r/,"\\r").gsub("\0","\\0")
|
|
814
|
+
end
|
|
815
|
+
|
|
816
|
+
##
|
|
817
|
+
# Removes minimal slashes to unescape the string
|
|
818
|
+
# @return [String] the string slightly unescaped.
|
|
819
|
+
def remove_slashes
|
|
820
|
+
self.gsub(/\\0/,"\0").gsub(/\\r/,"\r").gsub(/\\n/,"\n").gsub(/\\\\/,"\\")
|
|
821
|
+
end
|
|
822
|
+
|
|
823
|
+
##
|
|
824
|
+
# returns the path as an absolute path with +root+
|
|
825
|
+
# ROOT MUST BE ABSOLUTE
|
|
826
|
+
#
|
|
827
|
+
# @param [String] root absolute path to the root
|
|
828
|
+
def absolute(root)
|
|
829
|
+
return self if self[0] == ?/
|
|
830
|
+
"#{root}/#{self}"
|
|
831
|
+
end
|
|
832
|
+
|
|
833
|
+
##
|
|
834
|
+
# Attempts to discern if the string represents binary data or not. Not 100% accurate.
|
|
835
|
+
# Is part of the YAML code that comes with ruby, but since we don't load rubygems,
|
|
836
|
+
# we don't get this method for free.
|
|
837
|
+
#
|
|
838
|
+
# @return [Boolean] is the string (most likely) binary data?
|
|
839
|
+
|
|
840
|
+
def is_binary_data?
|
|
841
|
+
( self.count( "^ -~", "^\r\n" ) / self.size > 0.3 || self.count( "\x00" ) > 0 ) unless empty?
|
|
842
|
+
end
|
|
843
|
+
alias_method :binary?, :is_binary_data?
|
|
844
|
+
end
|
|
845
|
+
|
|
846
|
+
class Time
|
|
847
|
+
##
|
|
848
|
+
# Returns the date in a format suitable for unified diffs.
|
|
849
|
+
#
|
|
850
|
+
# @return [String] diff format: 2009-03-28 18:45:12.541298
|
|
851
|
+
def to_diff
|
|
852
|
+
strftime("%Y-%m-%d %H:%M:%S.#{usec}")
|
|
853
|
+
end
|
|
854
|
+
|
|
855
|
+
# Returns a nifty date stamp for certain diff types. not used yet.
|
|
856
|
+
def date_str(offset=0, format="%a %b %d %H:%M:%S %Y %1%2")
|
|
857
|
+
t, tz = self, offset
|
|
858
|
+
if format =~ /%1/ || format =~ /%2/
|
|
859
|
+
sign = (tz > 0) ? "-" : "+"
|
|
860
|
+
minutes = tz.abs / 60
|
|
861
|
+
format.gsub!(/%1/, "#{sign}#{(minutes / 60).to_s.rjust(2,'0')}")
|
|
862
|
+
format.gsub!(/%2/, "#{(minutes % 60).to_s.rjust(2,'0')}")
|
|
863
|
+
end
|
|
864
|
+
(self - tz).gmtime.strftime(format)
|
|
865
|
+
end
|
|
866
|
+
|
|
867
|
+
end
|
|
868
|
+
|
|
869
|
+
class Proc
|
|
870
|
+
|
|
871
|
+
##
|
|
872
|
+
# Alias for #call, pretty much.
|
|
873
|
+
#
|
|
874
|
+
# @param [Hash] options hash of the options for the command
|
|
875
|
+
# @param [Array] args array of extra args
|
|
876
|
+
def run(options={}, args=[])
|
|
877
|
+
call options, args
|
|
878
|
+
end
|
|
879
|
+
end
|
|
880
|
+
|
|
881
|
+
class Symbol
|
|
882
|
+
|
|
883
|
+
# Converts the symbol to an integer used for tracking the state
|
|
884
|
+
# of files in the dir_state.
|
|
885
|
+
def to_hg_int
|
|
886
|
+
case self
|
|
887
|
+
when :normal, :dirty
|
|
888
|
+
110 # "n".ord
|
|
889
|
+
when :untracked
|
|
890
|
+
63 # "?".ord
|
|
891
|
+
when :added
|
|
892
|
+
97 # "a".ord
|
|
893
|
+
when :removed
|
|
894
|
+
114 # "r".ord
|
|
895
|
+
when :merged
|
|
896
|
+
109 # "m".ord
|
|
897
|
+
else
|
|
898
|
+
raise "No known hg value for #{self}"
|
|
899
|
+
end
|
|
900
|
+
end
|
|
901
|
+
|
|
902
|
+
# Converts the symbol to the letter it corresponds to
|
|
903
|
+
def to_hg_letter
|
|
904
|
+
to_hg_int.chr
|
|
905
|
+
end
|
|
906
|
+
|
|
907
|
+
def to_proc
|
|
908
|
+
proc do |arg|
|
|
909
|
+
arg.send self
|
|
910
|
+
end
|
|
911
|
+
end
|
|
912
|
+
end
|
|
913
|
+
|
|
914
|
+
# net_digest_auth.rb
|
|
915
|
+
|
|
916
|
+
module Net
|
|
917
|
+
autoload :HTTP, 'net/http'
|
|
918
|
+
autoload :HTTPS, 'net/https'
|
|
919
|
+
# Written by Eric Hodel <drbrain@segment7.net>
|
|
920
|
+
module HTTPHeader
|
|
921
|
+
@@nonce_count = -1
|
|
922
|
+
CNONCE = Digest::MD5.new.update("%x" % (Time.now.to_i + rand(65535))).hexdigest
|
|
923
|
+
def digest_auth(user, password, response)
|
|
924
|
+
# based on http://segment7.net/projects/ruby/snippets/digest_auth.rb
|
|
925
|
+
@@nonce_count += 1
|
|
926
|
+
|
|
927
|
+
response['www-authenticate'] =~ /^(\w+) (.*)/
|
|
928
|
+
|
|
929
|
+
params = {}
|
|
930
|
+
$2.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 }
|
|
931
|
+
|
|
932
|
+
a_1 = "#{user}:#{params['realm']}:#{password}"
|
|
933
|
+
a_2 = "#{@method}:#{@path}"
|
|
934
|
+
request_digest = ''
|
|
935
|
+
request_digest << Digest::MD5.new.update(a_1).hexdigest
|
|
936
|
+
request_digest << ':' << params['nonce']
|
|
937
|
+
request_digest << ':' << ('%08x' % @@nonce_count)
|
|
938
|
+
request_digest << ':' << CNONCE
|
|
939
|
+
request_digest << ':' << params['qop']
|
|
940
|
+
request_digest << ':' << Digest::MD5.new.update(a_2).hexdigest
|
|
941
|
+
|
|
942
|
+
header = []
|
|
943
|
+
header << "Digest username=\"#{user}\""
|
|
944
|
+
header << "realm=\"#{params['realm']}\""
|
|
945
|
+
|
|
946
|
+
header << "qop=#{params['qop']}"
|
|
947
|
+
|
|
948
|
+
header << "algorithm=MD5"
|
|
949
|
+
header << "uri=\"#{@path}\""
|
|
950
|
+
header << "nonce=\"#{params['nonce']}\""
|
|
951
|
+
header << "nc=#{'%08x' % @@nonce_count}"
|
|
952
|
+
header << "cnonce=\"#{CNONCE}\""
|
|
953
|
+
header << "response=\"#{Digest::MD5.new.update(request_digest).hexdigest}\""
|
|
954
|
+
|
|
955
|
+
@header['Authorization'] = header
|
|
956
|
+
end
|
|
957
|
+
end
|
|
958
|
+
end
|
|
959
|
+
|
|
960
|
+
module Amp
|
|
961
|
+
module Support
|
|
962
|
+
SYSTEM = {}
|
|
963
|
+
UMASK = File.umask
|
|
964
|
+
|
|
965
|
+
@@rc_path = nil
|
|
966
|
+
# Returns all paths to hgrc files on the system.
|
|
967
|
+
def self.rc_path
|
|
968
|
+
if @@rc_path.nil?
|
|
969
|
+
if ENV['HGRCPATH']
|
|
970
|
+
@@rc_path = []
|
|
971
|
+
ENV['HGRCPATH'].split(File::PATH_SEPARATOR).each do |p|
|
|
972
|
+
next if p.empty?
|
|
973
|
+
if File.directory?(p)
|
|
974
|
+
File.stat_list(p) do |f, kind|
|
|
975
|
+
if f =~ /\.rc$/
|
|
976
|
+
@@rc_path << File.join(p, f)
|
|
977
|
+
end
|
|
978
|
+
end
|
|
979
|
+
else
|
|
980
|
+
@@rc_path << p
|
|
981
|
+
end
|
|
982
|
+
end
|
|
983
|
+
else
|
|
984
|
+
@@rc_path = self.os_rcpath
|
|
985
|
+
end
|
|
986
|
+
end
|
|
987
|
+
@@rc_path
|
|
988
|
+
end
|
|
989
|
+
|
|
990
|
+
|
|
991
|
+
##
|
|
992
|
+
# Advanced calling of system().
|
|
993
|
+
#
|
|
994
|
+
# Allows the caller to provide substitute environment variables and
|
|
995
|
+
# the directory to use
|
|
996
|
+
def self.system(command, opts={})
|
|
997
|
+
backup_dir = Dir.pwd # in case something goes wrong
|
|
998
|
+
temp_environ, temp_path = opts.delete(:environ), opts.delete(:chdir) || backup_dir
|
|
999
|
+
|
|
1000
|
+
if (temp_environ)
|
|
1001
|
+
old_env = ENV.to_hash
|
|
1002
|
+
temp_environ["HG"] = $amp_executable || File.amp_find_executable("amp")
|
|
1003
|
+
temp_environ.each {|k, v| ENV[k] = v.to_s}
|
|
1004
|
+
end
|
|
1005
|
+
Dir.chdir(temp_path) do
|
|
1006
|
+
rc = Kernel::system(command)
|
|
1007
|
+
end
|
|
1008
|
+
ensure
|
|
1009
|
+
ENV.clear.update(old_env) if temp_environ
|
|
1010
|
+
Dir.chdir(backup_dir)
|
|
1011
|
+
end
|
|
1012
|
+
|
|
1013
|
+
##
|
|
1014
|
+
# Parses the URL for amp-specific reasons.
|
|
1015
|
+
#
|
|
1016
|
+
# @param [String] url The url to parse.
|
|
1017
|
+
# @param [Array] revs The revisions that will be used for this operation.
|
|
1018
|
+
# @return [Hash] A hash, specifying :url, :revs, and :head
|
|
1019
|
+
def self.parse_hg_url(url, revs=nil)
|
|
1020
|
+
revs ||= [] # in case nil is passed
|
|
1021
|
+
|
|
1022
|
+
unless url =~ /#/
|
|
1023
|
+
hds = revs.any? ? revs : nil
|
|
1024
|
+
return {:url => url, :revs => hds, :head => revs[-1]}
|
|
1025
|
+
end
|
|
1026
|
+
|
|
1027
|
+
url, branch = url.split('#')[0..1]
|
|
1028
|
+
checkout = revs[-1] || branch
|
|
1029
|
+
{:url => url, :revs => revs + [branch], :head => checkout}
|
|
1030
|
+
end
|
|
1031
|
+
# Returns the paths to hgrc files, specific to this type of system.
|
|
1032
|
+
def self.os_rcpath
|
|
1033
|
+
path = system_rcpath
|
|
1034
|
+
path += user_rcpath
|
|
1035
|
+
path.map! {|f| File.expand_path f}
|
|
1036
|
+
path
|
|
1037
|
+
end
|
|
1038
|
+
|
|
1039
|
+
# Returns the hgrc files for the current user, specific to the particular
|
|
1040
|
+
# OS and user.
|
|
1041
|
+
def self.user_rcpath
|
|
1042
|
+
[File.expand_path("~/.hgrc")]
|
|
1043
|
+
end
|
|
1044
|
+
|
|
1045
|
+
# Returns all hgrc files for the given path
|
|
1046
|
+
def self.rc_files_for_path path
|
|
1047
|
+
rcs = [File.join(path, "hgrc")]
|
|
1048
|
+
rcdir = File.join(path, "hgrc.d")
|
|
1049
|
+
begin
|
|
1050
|
+
Dir.stat_list(rcdir) {|f, kind| rcs << File.join(rcdir, f) if f =~ /\.rc$/}
|
|
1051
|
+
rescue
|
|
1052
|
+
end
|
|
1053
|
+
rcs
|
|
1054
|
+
end
|
|
1055
|
+
|
|
1056
|
+
# gets the logged-in username
|
|
1057
|
+
def self.get_username
|
|
1058
|
+
Etc.getlogin
|
|
1059
|
+
end
|
|
1060
|
+
|
|
1061
|
+
# gets the fully-qualified-domain-name for fake usernames
|
|
1062
|
+
def self.get_fully_qualified_domain_name
|
|
1063
|
+
require 'socket'
|
|
1064
|
+
Socket.gethostbyname(Socket.gethostname).first
|
|
1065
|
+
end
|
|
1066
|
+
|
|
1067
|
+
# Returns the hgrc paths specific to this type of system, and are
|
|
1068
|
+
# system-wide.
|
|
1069
|
+
def self.system_rcpath
|
|
1070
|
+
path = []
|
|
1071
|
+
if ARGV.size > 0
|
|
1072
|
+
path += rc_files_for_path(File.dirname(ARGV[0]) + "/../etc/mercurial")
|
|
1073
|
+
end
|
|
1074
|
+
path += rc_files_for_path "/etc/mercurial"
|
|
1075
|
+
path
|
|
1076
|
+
end
|
|
1077
|
+
|
|
1078
|
+
# Figures up the system is running on a little or big endian processor
|
|
1079
|
+
# architecture, and upates the SYSTEM[] hash in the Support module.
|
|
1080
|
+
def self.determine_endianness
|
|
1081
|
+
num = 0x12345678
|
|
1082
|
+
little = '78563412'
|
|
1083
|
+
big = '12345678'
|
|
1084
|
+
native = [num].pack('l')
|
|
1085
|
+
netunpack = native.unpack('N')
|
|
1086
|
+
if native == netunpack
|
|
1087
|
+
SYSTEM[:endian] = :big
|
|
1088
|
+
else
|
|
1089
|
+
SYSTEM[:endian] = :little
|
|
1090
|
+
end
|
|
1091
|
+
end
|
|
1092
|
+
|
|
1093
|
+
determine_endianness
|
|
1094
|
+
end
|
|
1095
|
+
end
|