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.
Files changed (295) hide show
  1. data/.gitignore +1 -0
  2. data/.hgignore +26 -0
  3. data/AUTHORS +2 -0
  4. data/History.txt +6 -0
  5. data/LICENSE +37 -0
  6. data/MANIFESTO +7 -0
  7. data/Manifest.txt +294 -0
  8. data/README.md +129 -0
  9. data/Rakefile +102 -0
  10. data/SCHEDULE.markdown +12 -0
  11. data/STYLE +27 -0
  12. data/TODO.markdown +149 -0
  13. data/ampfile.rb +47 -0
  14. data/bin/amp +30 -0
  15. data/bin/amp1.9 +30 -0
  16. data/ext/amp/bz2/README.txt +39 -0
  17. data/ext/amp/bz2/bz2.c +1582 -0
  18. data/ext/amp/bz2/extconf.rb +77 -0
  19. data/ext/amp/bz2/mkmf.log +29 -0
  20. data/ext/amp/mercurial_patch/extconf.rb +5 -0
  21. data/ext/amp/mercurial_patch/mpatch.c +405 -0
  22. data/ext/amp/priority_queue/extconf.rb +5 -0
  23. data/ext/amp/priority_queue/priority_queue.c +947 -0
  24. data/ext/amp/support/extconf.rb +5 -0
  25. data/ext/amp/support/support.c +250 -0
  26. data/lib/amp.rb +200 -0
  27. data/lib/amp/commands/command.rb +507 -0
  28. data/lib/amp/commands/command_support.rb +137 -0
  29. data/lib/amp/commands/commands/config.rb +143 -0
  30. data/lib/amp/commands/commands/help.rb +29 -0
  31. data/lib/amp/commands/commands/init.rb +10 -0
  32. data/lib/amp/commands/commands/templates.rb +137 -0
  33. data/lib/amp/commands/commands/version.rb +7 -0
  34. data/lib/amp/commands/commands/workflow.rb +28 -0
  35. data/lib/amp/commands/commands/workflows/git/add.rb +65 -0
  36. data/lib/amp/commands/commands/workflows/git/copy.rb +27 -0
  37. data/lib/amp/commands/commands/workflows/git/mv.rb +23 -0
  38. data/lib/amp/commands/commands/workflows/git/rm.rb +60 -0
  39. data/lib/amp/commands/commands/workflows/hg/add.rb +53 -0
  40. data/lib/amp/commands/commands/workflows/hg/addremove.rb +86 -0
  41. data/lib/amp/commands/commands/workflows/hg/annotate.rb +46 -0
  42. data/lib/amp/commands/commands/workflows/hg/archive.rb +126 -0
  43. data/lib/amp/commands/commands/workflows/hg/branch.rb +28 -0
  44. data/lib/amp/commands/commands/workflows/hg/branches.rb +30 -0
  45. data/lib/amp/commands/commands/workflows/hg/bundle.rb +115 -0
  46. data/lib/amp/commands/commands/workflows/hg/clone.rb +95 -0
  47. data/lib/amp/commands/commands/workflows/hg/commit.rb +42 -0
  48. data/lib/amp/commands/commands/workflows/hg/copy.rb +31 -0
  49. data/lib/amp/commands/commands/workflows/hg/debug/dirstate.rb +32 -0
  50. data/lib/amp/commands/commands/workflows/hg/debug/index.rb +36 -0
  51. data/lib/amp/commands/commands/workflows/hg/default.rb +9 -0
  52. data/lib/amp/commands/commands/workflows/hg/diff.rb +30 -0
  53. data/lib/amp/commands/commands/workflows/hg/forget.rb +11 -0
  54. data/lib/amp/commands/commands/workflows/hg/heads.rb +25 -0
  55. data/lib/amp/commands/commands/workflows/hg/identify.rb +23 -0
  56. data/lib/amp/commands/commands/workflows/hg/import.rb +135 -0
  57. data/lib/amp/commands/commands/workflows/hg/incoming.rb +85 -0
  58. data/lib/amp/commands/commands/workflows/hg/info.rb +18 -0
  59. data/lib/amp/commands/commands/workflows/hg/log.rb +21 -0
  60. data/lib/amp/commands/commands/workflows/hg/manifest.rb +13 -0
  61. data/lib/amp/commands/commands/workflows/hg/merge.rb +53 -0
  62. data/lib/amp/commands/commands/workflows/hg/move.rb +28 -0
  63. data/lib/amp/commands/commands/workflows/hg/outgoing.rb +61 -0
  64. data/lib/amp/commands/commands/workflows/hg/pull.rb +74 -0
  65. data/lib/amp/commands/commands/workflows/hg/push.rb +20 -0
  66. data/lib/amp/commands/commands/workflows/hg/remove.rb +45 -0
  67. data/lib/amp/commands/commands/workflows/hg/resolve.rb +83 -0
  68. data/lib/amp/commands/commands/workflows/hg/revert.rb +53 -0
  69. data/lib/amp/commands/commands/workflows/hg/root.rb +13 -0
  70. data/lib/amp/commands/commands/workflows/hg/serve.rb +38 -0
  71. data/lib/amp/commands/commands/workflows/hg/status.rb +116 -0
  72. data/lib/amp/commands/commands/workflows/hg/tag.rb +69 -0
  73. data/lib/amp/commands/commands/workflows/hg/tags.rb +27 -0
  74. data/lib/amp/commands/commands/workflows/hg/tip.rb +13 -0
  75. data/lib/amp/commands/commands/workflows/hg/update.rb +27 -0
  76. data/lib/amp/commands/commands/workflows/hg/verify.rb +9 -0
  77. data/lib/amp/commands/commands/workflows/hg/view.rb +36 -0
  78. data/lib/amp/commands/dispatch.rb +181 -0
  79. data/lib/amp/commands/hooks.rb +81 -0
  80. data/lib/amp/dependencies/amp_support.rb +1 -0
  81. data/lib/amp/dependencies/amp_support/ruby_amp_support.rb +103 -0
  82. data/lib/amp/dependencies/minitar.rb +979 -0
  83. data/lib/amp/dependencies/priority_queue.rb +18 -0
  84. data/lib/amp/dependencies/priority_queue/c_priority_queue.rb +1 -0
  85. data/lib/amp/dependencies/priority_queue/poor_priority_queue.rb +46 -0
  86. data/lib/amp/dependencies/priority_queue/ruby_priority_queue.rb +525 -0
  87. data/lib/amp/dependencies/python_config.rb +211 -0
  88. data/lib/amp/dependencies/trollop.rb +713 -0
  89. data/lib/amp/dependencies/zip/ioextras.rb +155 -0
  90. data/lib/amp/dependencies/zip/stdrubyext.rb +111 -0
  91. data/lib/amp/dependencies/zip/tempfile_bugfixed.rb +186 -0
  92. data/lib/amp/dependencies/zip/zip.rb +1850 -0
  93. data/lib/amp/dependencies/zip/zipfilesystem.rb +609 -0
  94. data/lib/amp/dependencies/zip/ziprequire.rb +90 -0
  95. data/lib/amp/encoding/base85.rb +97 -0
  96. data/lib/amp/encoding/binary_diff.rb +82 -0
  97. data/lib/amp/encoding/difflib.rb +166 -0
  98. data/lib/amp/encoding/mercurial_diff.rb +378 -0
  99. data/lib/amp/encoding/mercurial_patch.rb +1 -0
  100. data/lib/amp/encoding/patch.rb +292 -0
  101. data/lib/amp/encoding/pure_ruby/ruby_mercurial_patch.rb +123 -0
  102. data/lib/amp/extensions/ditz.rb +41 -0
  103. data/lib/amp/extensions/lighthouse.rb +167 -0
  104. data/lib/amp/graphs/ancestor.rb +147 -0
  105. data/lib/amp/graphs/copies.rb +261 -0
  106. data/lib/amp/merges/merge_state.rb +164 -0
  107. data/lib/amp/merges/merge_ui.rb +322 -0
  108. data/lib/amp/merges/simple_merge.rb +450 -0
  109. data/lib/amp/profiling_hacks.rb +36 -0
  110. data/lib/amp/repository/branch_manager.rb +234 -0
  111. data/lib/amp/repository/dir_state.rb +950 -0
  112. data/lib/amp/repository/journal.rb +203 -0
  113. data/lib/amp/repository/lock.rb +207 -0
  114. data/lib/amp/repository/repositories/bundle_repository.rb +214 -0
  115. data/lib/amp/repository/repositories/http_repository.rb +377 -0
  116. data/lib/amp/repository/repositories/local_repository.rb +2661 -0
  117. data/lib/amp/repository/repository.rb +94 -0
  118. data/lib/amp/repository/store.rb +485 -0
  119. data/lib/amp/repository/tag_manager.rb +319 -0
  120. data/lib/amp/repository/updatable.rb +532 -0
  121. data/lib/amp/repository/verification.rb +431 -0
  122. data/lib/amp/repository/versioned_file.rb +475 -0
  123. data/lib/amp/revlogs/bundle_revlogs.rb +246 -0
  124. data/lib/amp/revlogs/changegroup.rb +217 -0
  125. data/lib/amp/revlogs/changelog.rb +338 -0
  126. data/lib/amp/revlogs/changeset.rb +521 -0
  127. data/lib/amp/revlogs/file_log.rb +165 -0
  128. data/lib/amp/revlogs/index.rb +493 -0
  129. data/lib/amp/revlogs/manifest.rb +195 -0
  130. data/lib/amp/revlogs/node.rb +18 -0
  131. data/lib/amp/revlogs/revlog.rb +1032 -0
  132. data/lib/amp/revlogs/revlog_support.rb +126 -0
  133. data/lib/amp/server/amp_user.rb +44 -0
  134. data/lib/amp/server/extension/amp_extension.rb +396 -0
  135. data/lib/amp/server/extension/authorization.rb +201 -0
  136. data/lib/amp/server/fancy_http_server.rb +252 -0
  137. data/lib/amp/server/fancy_views/_browser.haml +28 -0
  138. data/lib/amp/server/fancy_views/_diff_file.haml +13 -0
  139. data/lib/amp/server/fancy_views/_navbar.haml +17 -0
  140. data/lib/amp/server/fancy_views/changeset.haml +31 -0
  141. data/lib/amp/server/fancy_views/commits.haml +32 -0
  142. data/lib/amp/server/fancy_views/file.haml +35 -0
  143. data/lib/amp/server/fancy_views/file_diff.haml +23 -0
  144. data/lib/amp/server/fancy_views/harshcss/all_hallows_eve.css +72 -0
  145. data/lib/amp/server/fancy_views/harshcss/amy.css +147 -0
  146. data/lib/amp/server/fancy_views/harshcss/twilight.css +138 -0
  147. data/lib/amp/server/fancy_views/stylesheet.sass +175 -0
  148. data/lib/amp/server/http_server.rb +140 -0
  149. data/lib/amp/server/repo_user_management.rb +287 -0
  150. data/lib/amp/support/amp_config.rb +164 -0
  151. data/lib/amp/support/amp_ui.rb +287 -0
  152. data/lib/amp/support/docs.rb +54 -0
  153. data/lib/amp/support/generator.rb +78 -0
  154. data/lib/amp/support/ignore.rb +144 -0
  155. data/lib/amp/support/loaders.rb +93 -0
  156. data/lib/amp/support/logger.rb +103 -0
  157. data/lib/amp/support/match.rb +151 -0
  158. data/lib/amp/support/multi_io.rb +87 -0
  159. data/lib/amp/support/openers.rb +121 -0
  160. data/lib/amp/support/ruby_19_compatibility.rb +66 -0
  161. data/lib/amp/support/support.rb +1095 -0
  162. data/lib/amp/templates/blank.commit.erb +23 -0
  163. data/lib/amp/templates/blank.log.erb +18 -0
  164. data/lib/amp/templates/default.commit.erb +23 -0
  165. data/lib/amp/templates/default.log.erb +26 -0
  166. data/lib/amp/templates/template.rb +165 -0
  167. data/site/Rakefile +24 -0
  168. data/site/src/about/ampfile.haml +57 -0
  169. data/site/src/about/commands.haml +106 -0
  170. data/site/src/about/index.haml +33 -0
  171. data/site/src/about/performance.haml +31 -0
  172. data/site/src/about/workflows.haml +34 -0
  173. data/site/src/contribute/index.haml +65 -0
  174. data/site/src/contribute/style.haml +297 -0
  175. data/site/src/css/active4d.css +114 -0
  176. data/site/src/css/all_hallows_eve.css +72 -0
  177. data/site/src/css/all_themes.css +3299 -0
  178. data/site/src/css/amp.css +260 -0
  179. data/site/src/css/amy.css +147 -0
  180. data/site/src/css/blackboard.css +88 -0
  181. data/site/src/css/brilliance_black.css +605 -0
  182. data/site/src/css/brilliance_dull.css +599 -0
  183. data/site/src/css/cobalt.css +149 -0
  184. data/site/src/css/cur_amp.css +185 -0
  185. data/site/src/css/dawn.css +121 -0
  186. data/site/src/css/eiffel.css +121 -0
  187. data/site/src/css/espresso_libre.css +109 -0
  188. data/site/src/css/idle.css +62 -0
  189. data/site/src/css/iplastic.css +80 -0
  190. data/site/src/css/lazy.css +73 -0
  191. data/site/src/css/mac_classic.css +123 -0
  192. data/site/src/css/magicwb_amiga.css +104 -0
  193. data/site/src/css/pastels_on_dark.css +188 -0
  194. data/site/src/css/reset.css +55 -0
  195. data/site/src/css/slush_poppies.css +85 -0
  196. data/site/src/css/spacecadet.css +51 -0
  197. data/site/src/css/sunburst.css +180 -0
  198. data/site/src/css/twilight.css +137 -0
  199. data/site/src/css/zenburnesque.css +91 -0
  200. data/site/src/get/index.haml +32 -0
  201. data/site/src/helpers.rb +121 -0
  202. data/site/src/images/amp_logo.png +0 -0
  203. data/site/src/images/carbonica.png +0 -0
  204. data/site/src/images/revolution.png +0 -0
  205. data/site/src/images/tab-bg.png +0 -0
  206. data/site/src/images/tab-sliding-left.png +0 -0
  207. data/site/src/images/tab-sliding-right.png +0 -0
  208. data/site/src/include/_footer.haml +22 -0
  209. data/site/src/include/_header.haml +17 -0
  210. data/site/src/index.haml +104 -0
  211. data/site/src/learn/index.haml +46 -0
  212. data/site/src/scripts/jquery-1.3.2.min.js +19 -0
  213. data/site/src/scripts/jquery.cookie.js +96 -0
  214. data/tasks/stats.rake +155 -0
  215. data/tasks/yard.rake +171 -0
  216. data/test/dirstate_tests/dirstate +0 -0
  217. data/test/dirstate_tests/hgrc +5 -0
  218. data/test/dirstate_tests/test_dir_state.rb +192 -0
  219. data/test/functional_tests/resources/.hgignore +2 -0
  220. data/test/functional_tests/resources/STYLE.txt +25 -0
  221. data/test/functional_tests/resources/command.rb +372 -0
  222. data/test/functional_tests/resources/commands/annotate.rb +57 -0
  223. data/test/functional_tests/resources/commands/experimental/lolcats.rb +17 -0
  224. data/test/functional_tests/resources/commands/heads.rb +22 -0
  225. data/test/functional_tests/resources/commands/manifest.rb +12 -0
  226. data/test/functional_tests/resources/commands/status.rb +90 -0
  227. data/test/functional_tests/resources/version2/.hgignore +5 -0
  228. data/test/functional_tests/resources/version2/STYLE.txt +25 -0
  229. data/test/functional_tests/resources/version2/command.rb +372 -0
  230. data/test/functional_tests/resources/version2/commands/annotate.rb +45 -0
  231. data/test/functional_tests/resources/version2/commands/experimental/lolcats.rb +17 -0
  232. data/test/functional_tests/resources/version2/commands/heads.rb +22 -0
  233. data/test/functional_tests/resources/version2/commands/manifest.rb +12 -0
  234. data/test/functional_tests/resources/version2/commands/status.rb +90 -0
  235. data/test/functional_tests/resources/version3/.hgignore +5 -0
  236. data/test/functional_tests/resources/version3/STYLE.txt +31 -0
  237. data/test/functional_tests/resources/version3/command.rb +376 -0
  238. data/test/functional_tests/resources/version3/commands/annotate.rb +45 -0
  239. data/test/functional_tests/resources/version3/commands/experimental/lolcats.rb +17 -0
  240. data/test/functional_tests/resources/version3/commands/heads.rb +22 -0
  241. data/test/functional_tests/resources/version3/commands/manifest.rb +12 -0
  242. data/test/functional_tests/resources/version3/commands/status.rb +90 -0
  243. data/test/functional_tests/resources/version4/.hgignore +5 -0
  244. data/test/functional_tests/resources/version4/STYLE.txt +31 -0
  245. data/test/functional_tests/resources/version4/command.rb +376 -0
  246. data/test/functional_tests/resources/version4/commands/experimental/lolcats.rb +17 -0
  247. data/test/functional_tests/resources/version4/commands/heads.rb +22 -0
  248. data/test/functional_tests/resources/version4/commands/manifest.rb +12 -0
  249. data/test/functional_tests/resources/version4/commands/stats.rb +25 -0
  250. data/test/functional_tests/resources/version4/commands/status.rb +90 -0
  251. data/test/functional_tests/resources/version5_1/.hgignore +5 -0
  252. data/test/functional_tests/resources/version5_1/STYLE.txt +2 -0
  253. data/test/functional_tests/resources/version5_1/command.rb +374 -0
  254. data/test/functional_tests/resources/version5_1/commands/experimental/lolcats.rb +17 -0
  255. data/test/functional_tests/resources/version5_1/commands/heads.rb +22 -0
  256. data/test/functional_tests/resources/version5_1/commands/manifest.rb +12 -0
  257. data/test/functional_tests/resources/version5_1/commands/stats.rb +25 -0
  258. data/test/functional_tests/resources/version5_1/commands/status.rb +90 -0
  259. data/test/functional_tests/resources/version5_2/.hgignore +5 -0
  260. data/test/functional_tests/resources/version5_2/STYLE.txt +14 -0
  261. data/test/functional_tests/resources/version5_2/command.rb +376 -0
  262. data/test/functional_tests/resources/version5_2/commands/experimental/lolcats.rb +17 -0
  263. data/test/functional_tests/resources/version5_2/commands/manifest.rb +12 -0
  264. data/test/functional_tests/resources/version5_2/commands/newz.rb +12 -0
  265. data/test/functional_tests/resources/version5_2/commands/stats.rb +25 -0
  266. data/test/functional_tests/resources/version5_2/commands/status.rb +90 -0
  267. data/test/functional_tests/test_functional.rb +604 -0
  268. data/test/localrepo_tests/test_local_repo.rb +121 -0
  269. data/test/localrepo_tests/testrepo.tar.gz +0 -0
  270. data/test/manifest_tests/00manifest.i +0 -0
  271. data/test/manifest_tests/test_manifest.rb +72 -0
  272. data/test/merge_tests/base.txt +10 -0
  273. data/test/merge_tests/expected.local.txt +16 -0
  274. data/test/merge_tests/local.txt +11 -0
  275. data/test/merge_tests/remote.txt +11 -0
  276. data/test/merge_tests/test_merge.rb +26 -0
  277. data/test/revlog_tests/00changelog.i +0 -0
  278. data/test/revlog_tests/revision_added_changelog.i +0 -0
  279. data/test/revlog_tests/test_adding_index.i +0 -0
  280. data/test/revlog_tests/test_revlog.rb +333 -0
  281. data/test/revlog_tests/testindex.i +0 -0
  282. data/test/store_tests/store.tar.gz +0 -0
  283. data/test/store_tests/test_fncache_store.rb +122 -0
  284. data/test/test_amp.rb +9 -0
  285. data/test/test_base85.rb +14 -0
  286. data/test/test_bdiff.rb +42 -0
  287. data/test/test_commands.rb +122 -0
  288. data/test/test_difflib.rb +50 -0
  289. data/test/test_helper.rb +15 -0
  290. data/test/test_journal.rb +29 -0
  291. data/test/test_match.rb +134 -0
  292. data/test/test_mdiff.rb +74 -0
  293. data/test/test_mpatch.rb +14 -0
  294. data/test/test_support.rb +24 -0
  295. 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