amp 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|