redsnow 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (174) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +34 -0
  3. data/.gitmodules +3 -0
  4. data/.travis.yml +20 -0
  5. data/CHANGELOG.md +4 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE +21 -0
  8. data/README.md +62 -0
  9. data/Rakefile +36 -0
  10. data/Vagrantfile +20 -0
  11. data/ext/snowcrash/Makefile +64 -0
  12. data/ext/snowcrash/Vagrantfile +20 -0
  13. data/ext/snowcrash/bin/snowcrash +0 -0
  14. data/ext/snowcrash/common.gypi +163 -0
  15. data/ext/snowcrash/config.gypi +10 -0
  16. data/ext/snowcrash/config.mk +5 -0
  17. data/ext/snowcrash/configure +213 -0
  18. data/ext/snowcrash/provisioning.sh +15 -0
  19. data/ext/snowcrash/snowcrash.gyp +141 -0
  20. data/ext/snowcrash/src/ActionParser.h +503 -0
  21. data/ext/snowcrash/src/AssetParser.h +215 -0
  22. data/ext/snowcrash/src/BlockUtility.h +186 -0
  23. data/ext/snowcrash/src/Blueprint.h +283 -0
  24. data/ext/snowcrash/src/BlueprintParser.h +347 -0
  25. data/ext/snowcrash/src/BlueprintParserCore.h +190 -0
  26. data/ext/snowcrash/src/BlueprintSection.h +140 -0
  27. data/ext/snowcrash/src/BlueprintUtility.h +126 -0
  28. data/ext/snowcrash/src/CBlueprint.cc +600 -0
  29. data/ext/snowcrash/src/CBlueprint.h +354 -0
  30. data/ext/snowcrash/src/CSourceAnnotation.cc +140 -0
  31. data/ext/snowcrash/src/CSourceAnnotation.h +106 -0
  32. data/ext/snowcrash/src/CodeBlockUtility.h +189 -0
  33. data/ext/snowcrash/src/DescriptionSectionUtility.h +156 -0
  34. data/ext/snowcrash/src/HTTP.cc +46 -0
  35. data/ext/snowcrash/src/HTTP.h +105 -0
  36. data/ext/snowcrash/src/HeaderParser.h +289 -0
  37. data/ext/snowcrash/src/ListBlockUtility.h +273 -0
  38. data/ext/snowcrash/src/ListUtility.h +95 -0
  39. data/ext/snowcrash/src/MarkdownBlock.cc +176 -0
  40. data/ext/snowcrash/src/MarkdownBlock.h +93 -0
  41. data/ext/snowcrash/src/MarkdownParser.cc +266 -0
  42. data/ext/snowcrash/src/MarkdownParser.h +88 -0
  43. data/ext/snowcrash/src/ParameterDefinitonParser.h +570 -0
  44. data/ext/snowcrash/src/ParametersParser.h +252 -0
  45. data/ext/snowcrash/src/Parser.cc +71 -0
  46. data/ext/snowcrash/src/Parser.h +29 -0
  47. data/ext/snowcrash/src/ParserCore.cc +120 -0
  48. data/ext/snowcrash/src/ParserCore.h +82 -0
  49. data/ext/snowcrash/src/PayloadParser.h +672 -0
  50. data/ext/snowcrash/src/Platform.h +54 -0
  51. data/ext/snowcrash/src/RegexMatch.h +32 -0
  52. data/ext/snowcrash/src/ResourceGroupParser.h +195 -0
  53. data/ext/snowcrash/src/ResourceParser.h +584 -0
  54. data/ext/snowcrash/src/SectionUtility.h +142 -0
  55. data/ext/snowcrash/src/Serialize.cc +52 -0
  56. data/ext/snowcrash/src/Serialize.h +69 -0
  57. data/ext/snowcrash/src/SerializeJSON.cc +601 -0
  58. data/ext/snowcrash/src/SerializeJSON.h +21 -0
  59. data/ext/snowcrash/src/SerializeYAML.cc +336 -0
  60. data/ext/snowcrash/src/SerializeYAML.h +21 -0
  61. data/ext/snowcrash/src/SourceAnnotation.h +177 -0
  62. data/ext/snowcrash/src/StringUtility.h +109 -0
  63. data/ext/snowcrash/src/SymbolTable.h +83 -0
  64. data/ext/snowcrash/src/UriTemplateParser.cc +195 -0
  65. data/ext/snowcrash/src/UriTemplateParser.h +243 -0
  66. data/ext/snowcrash/src/Version.h +39 -0
  67. data/ext/snowcrash/src/csnowcrash.cc +23 -0
  68. data/ext/snowcrash/src/csnowcrash.h +38 -0
  69. data/ext/snowcrash/src/posix/RegexMatch.cc +99 -0
  70. data/ext/snowcrash/src/snowcrash.cc +18 -0
  71. data/ext/snowcrash/src/snowcrash.h +41 -0
  72. data/ext/snowcrash/src/snowcrash/snowcrash.cc +170 -0
  73. data/ext/snowcrash/src/win/RegexMatch.cc +78 -0
  74. data/ext/snowcrash/sundown/CONTRIBUTING.md +10 -0
  75. data/ext/snowcrash/sundown/Makefile +83 -0
  76. data/ext/snowcrash/sundown/Makefile.win +33 -0
  77. data/ext/snowcrash/sundown/examples/smartypants.c +72 -0
  78. data/ext/snowcrash/sundown/examples/sundown.c +80 -0
  79. data/ext/snowcrash/sundown/html/houdini.h +37 -0
  80. data/ext/snowcrash/sundown/html/houdini_href_e.c +108 -0
  81. data/ext/snowcrash/sundown/html/houdini_html_e.c +84 -0
  82. data/ext/snowcrash/sundown/html/html.c +647 -0
  83. data/ext/snowcrash/sundown/html/html.h +77 -0
  84. data/ext/snowcrash/sundown/html/html_smartypants.c +389 -0
  85. data/ext/snowcrash/sundown/html_block_names.txt +25 -0
  86. data/ext/snowcrash/sundown/src/autolink.c +297 -0
  87. data/ext/snowcrash/sundown/src/autolink.h +51 -0
  88. data/ext/snowcrash/sundown/src/buffer.c +225 -0
  89. data/ext/snowcrash/sundown/src/buffer.h +96 -0
  90. data/ext/snowcrash/sundown/src/html_blocks.h +206 -0
  91. data/ext/snowcrash/sundown/src/markdown.c +2701 -0
  92. data/ext/snowcrash/sundown/src/markdown.h +147 -0
  93. data/ext/snowcrash/sundown/src/src_map.c +200 -0
  94. data/ext/snowcrash/sundown/src/src_map.h +58 -0
  95. data/ext/snowcrash/sundown/src/stack.c +81 -0
  96. data/ext/snowcrash/sundown/src/stack.h +29 -0
  97. data/ext/snowcrash/sundown/sundown.def +20 -0
  98. data/ext/snowcrash/tools/gyp/AUTHORS +11 -0
  99. data/ext/snowcrash/tools/gyp/DEPS +24 -0
  100. data/ext/snowcrash/tools/gyp/OWNERS +1 -0
  101. data/ext/snowcrash/tools/gyp/PRESUBMIT.py +120 -0
  102. data/ext/snowcrash/tools/gyp/buildbot/buildbot_run.py +190 -0
  103. data/ext/snowcrash/tools/gyp/codereview.settings +10 -0
  104. data/ext/snowcrash/tools/gyp/data/win/large-pdb-shim.cc +12 -0
  105. data/ext/snowcrash/tools/gyp/gyp +8 -0
  106. data/ext/snowcrash/tools/gyp/gyp.bat +5 -0
  107. data/ext/snowcrash/tools/gyp/gyp_main.py +18 -0
  108. data/ext/snowcrash/tools/gyp/pylib/gyp/MSVSNew.py +340 -0
  109. data/ext/snowcrash/tools/gyp/pylib/gyp/MSVSProject.py +208 -0
  110. data/ext/snowcrash/tools/gyp/pylib/gyp/MSVSSettings.py +1063 -0
  111. data/ext/snowcrash/tools/gyp/pylib/gyp/MSVSToolFile.py +58 -0
  112. data/ext/snowcrash/tools/gyp/pylib/gyp/MSVSUserFile.py +147 -0
  113. data/ext/snowcrash/tools/gyp/pylib/gyp/MSVSUtil.py +267 -0
  114. data/ext/snowcrash/tools/gyp/pylib/gyp/MSVSVersion.py +409 -0
  115. data/ext/snowcrash/tools/gyp/pylib/gyp/__init__.py +537 -0
  116. data/ext/snowcrash/tools/gyp/pylib/gyp/__init__.pyc +0 -0
  117. data/ext/snowcrash/tools/gyp/pylib/gyp/common.py +521 -0
  118. data/ext/snowcrash/tools/gyp/pylib/gyp/common.pyc +0 -0
  119. data/ext/snowcrash/tools/gyp/pylib/gyp/easy_xml.py +157 -0
  120. data/ext/snowcrash/tools/gyp/pylib/gyp/flock_tool.py +49 -0
  121. data/ext/snowcrash/tools/gyp/pylib/gyp/generator/__init__.py +0 -0
  122. data/ext/snowcrash/tools/gyp/pylib/gyp/generator/__init__.pyc +0 -0
  123. data/ext/snowcrash/tools/gyp/pylib/gyp/generator/android.py +1069 -0
  124. data/ext/snowcrash/tools/gyp/pylib/gyp/generator/cmake.py +1143 -0
  125. data/ext/snowcrash/tools/gyp/pylib/gyp/generator/dump_dependency_json.py +81 -0
  126. data/ext/snowcrash/tools/gyp/pylib/gyp/generator/eclipse.py +335 -0
  127. data/ext/snowcrash/tools/gyp/pylib/gyp/generator/gypd.py +87 -0
  128. data/ext/snowcrash/tools/gyp/pylib/gyp/generator/gypsh.py +56 -0
  129. data/ext/snowcrash/tools/gyp/pylib/gyp/generator/make.py +2181 -0
  130. data/ext/snowcrash/tools/gyp/pylib/gyp/generator/make.pyc +0 -0
  131. data/ext/snowcrash/tools/gyp/pylib/gyp/generator/msvs.py +3335 -0
  132. data/ext/snowcrash/tools/gyp/pylib/gyp/generator/ninja.py +2156 -0
  133. data/ext/snowcrash/tools/gyp/pylib/gyp/generator/xcode.py +1224 -0
  134. data/ext/snowcrash/tools/gyp/pylib/gyp/generator/xcode.pyc +0 -0
  135. data/ext/snowcrash/tools/gyp/pylib/gyp/input.py +2809 -0
  136. data/ext/snowcrash/tools/gyp/pylib/gyp/input.pyc +0 -0
  137. data/ext/snowcrash/tools/gyp/pylib/gyp/mac_tool.py +510 -0
  138. data/ext/snowcrash/tools/gyp/pylib/gyp/msvs_emulation.py +972 -0
  139. data/ext/snowcrash/tools/gyp/pylib/gyp/ninja_syntax.py +160 -0
  140. data/ext/snowcrash/tools/gyp/pylib/gyp/ordered_dict.py +289 -0
  141. data/ext/snowcrash/tools/gyp/pylib/gyp/win_tool.py +292 -0
  142. data/ext/snowcrash/tools/gyp/pylib/gyp/xcode_emulation.py +1440 -0
  143. data/ext/snowcrash/tools/gyp/pylib/gyp/xcode_emulation.pyc +0 -0
  144. data/ext/snowcrash/tools/gyp/pylib/gyp/xcodeproj_file.py +2889 -0
  145. data/ext/snowcrash/tools/gyp/pylib/gyp/xcodeproj_file.pyc +0 -0
  146. data/ext/snowcrash/tools/gyp/pylib/gyp/xml_fix.py +69 -0
  147. data/ext/snowcrash/tools/gyp/pylintrc +307 -0
  148. data/ext/snowcrash/tools/gyp/samples/samples +81 -0
  149. data/ext/snowcrash/tools/gyp/samples/samples.bat +5 -0
  150. data/ext/snowcrash/tools/gyp/setup.py +19 -0
  151. data/ext/snowcrash/tools/gyp/tools/Xcode/Specifications/gyp.pbfilespec +27 -0
  152. data/ext/snowcrash/tools/gyp/tools/Xcode/Specifications/gyp.xclangspec +226 -0
  153. data/ext/snowcrash/tools/gyp/tools/emacs/gyp.el +252 -0
  154. data/ext/snowcrash/tools/gyp/tools/graphviz.py +100 -0
  155. data/ext/snowcrash/tools/gyp/tools/pretty_gyp.py +155 -0
  156. data/ext/snowcrash/tools/gyp/tools/pretty_sln.py +168 -0
  157. data/ext/snowcrash/tools/gyp/tools/pretty_vcproj.py +329 -0
  158. data/ext/snowcrash/tools/homebrew/snowcrash.rb +11 -0
  159. data/ext/snowcrash/vcbuild.bat +184 -0
  160. data/lib/redsnow.rb +31 -0
  161. data/lib/redsnow/binding.rb +132 -0
  162. data/lib/redsnow/blueprint.rb +365 -0
  163. data/lib/redsnow/object.rb +18 -0
  164. data/lib/redsnow/parseresult.rb +107 -0
  165. data/lib/redsnow/version.rb +4 -0
  166. data/provisioning.sh +20 -0
  167. data/redsnow.gemspec +35 -0
  168. data/test/_helper.rb +15 -0
  169. data/test/fixtures/sample-api-ast.json +97 -0
  170. data/test/fixtures/sample-api.apib +20 -0
  171. data/test/redsnow_binding_test.rb +35 -0
  172. data/test/redsnow_parseresult_test.rb +50 -0
  173. data/test/redsnow_test.rb +285 -0
  174. metadata +358 -0
@@ -0,0 +1,521 @@
1
+ # Copyright (c) 2012 Google Inc. All rights reserved.
2
+ # Use of this source code is governed by a BSD-style license that can be
3
+ # found in the LICENSE file.
4
+
5
+ from __future__ import with_statement
6
+
7
+ import errno
8
+ import filecmp
9
+ import os.path
10
+ import re
11
+ import tempfile
12
+ import sys
13
+
14
+
15
+ # A minimal memoizing decorator. It'll blow up if the args aren't immutable,
16
+ # among other "problems".
17
+ class memoize(object):
18
+ def __init__(self, func):
19
+ self.func = func
20
+ self.cache = {}
21
+ def __call__(self, *args):
22
+ try:
23
+ return self.cache[args]
24
+ except KeyError:
25
+ result = self.func(*args)
26
+ self.cache[args] = result
27
+ return result
28
+
29
+
30
+ class GypError(Exception):
31
+ """Error class representing an error, which is to be presented
32
+ to the user. The main entry point will catch and display this.
33
+ """
34
+ pass
35
+
36
+
37
+ def ExceptionAppend(e, msg):
38
+ """Append a message to the given exception's message."""
39
+ if not e.args:
40
+ e.args = (msg,)
41
+ elif len(e.args) == 1:
42
+ e.args = (str(e.args[0]) + ' ' + msg,)
43
+ else:
44
+ e.args = (str(e.args[0]) + ' ' + msg,) + e.args[1:]
45
+
46
+
47
+ def FindQualifiedTargets(target, qualified_list):
48
+ """
49
+ Given a list of qualified targets, return the qualified targets for the
50
+ specified |target|.
51
+ """
52
+ return [t for t in qualified_list if ParseQualifiedTarget(t)[1] == target]
53
+
54
+
55
+ def ParseQualifiedTarget(target):
56
+ # Splits a qualified target into a build file, target name and toolset.
57
+
58
+ # NOTE: rsplit is used to disambiguate the Windows drive letter separator.
59
+ target_split = target.rsplit(':', 1)
60
+ if len(target_split) == 2:
61
+ [build_file, target] = target_split
62
+ else:
63
+ build_file = None
64
+
65
+ target_split = target.rsplit('#', 1)
66
+ if len(target_split) == 2:
67
+ [target, toolset] = target_split
68
+ else:
69
+ toolset = None
70
+
71
+ return [build_file, target, toolset]
72
+
73
+
74
+ def ResolveTarget(build_file, target, toolset):
75
+ # This function resolves a target into a canonical form:
76
+ # - a fully defined build file, either absolute or relative to the current
77
+ # directory
78
+ # - a target name
79
+ # - a toolset
80
+ #
81
+ # build_file is the file relative to which 'target' is defined.
82
+ # target is the qualified target.
83
+ # toolset is the default toolset for that target.
84
+ [parsed_build_file, target, parsed_toolset] = ParseQualifiedTarget(target)
85
+
86
+ if parsed_build_file:
87
+ if build_file:
88
+ # If a relative path, parsed_build_file is relative to the directory
89
+ # containing build_file. If build_file is not in the current directory,
90
+ # parsed_build_file is not a usable path as-is. Resolve it by
91
+ # interpreting it as relative to build_file. If parsed_build_file is
92
+ # absolute, it is usable as a path regardless of the current directory,
93
+ # and os.path.join will return it as-is.
94
+ build_file = os.path.normpath(os.path.join(os.path.dirname(build_file),
95
+ parsed_build_file))
96
+ # Further (to handle cases like ../cwd), make it relative to cwd)
97
+ if not os.path.isabs(build_file):
98
+ build_file = RelativePath(build_file, '.')
99
+ else:
100
+ build_file = parsed_build_file
101
+
102
+ if parsed_toolset:
103
+ toolset = parsed_toolset
104
+
105
+ return [build_file, target, toolset]
106
+
107
+
108
+ def BuildFile(fully_qualified_target):
109
+ # Extracts the build file from the fully qualified target.
110
+ return ParseQualifiedTarget(fully_qualified_target)[0]
111
+
112
+
113
+ def GetEnvironFallback(var_list, default):
114
+ """Look up a key in the environment, with fallback to secondary keys
115
+ and finally falling back to a default value."""
116
+ for var in var_list:
117
+ if var in os.environ:
118
+ return os.environ[var]
119
+ return default
120
+
121
+
122
+ def QualifiedTarget(build_file, target, toolset):
123
+ # "Qualified" means the file that a target was defined in and the target
124
+ # name, separated by a colon, suffixed by a # and the toolset name:
125
+ # /path/to/file.gyp:target_name#toolset
126
+ fully_qualified = build_file + ':' + target
127
+ if toolset:
128
+ fully_qualified = fully_qualified + '#' + toolset
129
+ return fully_qualified
130
+
131
+
132
+ @memoize
133
+ def RelativePath(path, relative_to):
134
+ # Assuming both |path| and |relative_to| are relative to the current
135
+ # directory, returns a relative path that identifies path relative to
136
+ # relative_to.
137
+
138
+ # Convert to normalized (and therefore absolute paths).
139
+ path = os.path.realpath(path)
140
+ relative_to = os.path.realpath(relative_to)
141
+
142
+ # On Windows, we can't create a relative path to a different drive, so just
143
+ # use the absolute path.
144
+ if sys.platform == 'win32':
145
+ if (os.path.splitdrive(path)[0].lower() !=
146
+ os.path.splitdrive(relative_to)[0].lower()):
147
+ return path
148
+
149
+ # Split the paths into components.
150
+ path_split = path.split(os.path.sep)
151
+ relative_to_split = relative_to.split(os.path.sep)
152
+
153
+ # Determine how much of the prefix the two paths share.
154
+ prefix_len = len(os.path.commonprefix([path_split, relative_to_split]))
155
+
156
+ # Put enough ".." components to back up out of relative_to to the common
157
+ # prefix, and then append the part of path_split after the common prefix.
158
+ relative_split = [os.path.pardir] * (len(relative_to_split) - prefix_len) + \
159
+ path_split[prefix_len:]
160
+
161
+ if len(relative_split) == 0:
162
+ # The paths were the same.
163
+ return ''
164
+
165
+ # Turn it back into a string and we're done.
166
+ return os.path.join(*relative_split)
167
+
168
+
169
+ @memoize
170
+ def InvertRelativePath(path, toplevel_dir=None):
171
+ """Given a path like foo/bar that is relative to toplevel_dir, return
172
+ the inverse relative path back to the toplevel_dir.
173
+
174
+ E.g. os.path.normpath(os.path.join(path, InvertRelativePath(path)))
175
+ should always produce the empty string, unless the path contains symlinks.
176
+ """
177
+ if not path:
178
+ return path
179
+ toplevel_dir = '.' if toplevel_dir is None else toplevel_dir
180
+ return RelativePath(toplevel_dir, os.path.join(toplevel_dir, path))
181
+
182
+
183
+ def FixIfRelativePath(path, relative_to):
184
+ # Like RelativePath but returns |path| unchanged if it is absolute.
185
+ if os.path.isabs(path):
186
+ return path
187
+ return RelativePath(path, relative_to)
188
+
189
+
190
+ def UnrelativePath(path, relative_to):
191
+ # Assuming that |relative_to| is relative to the current directory, and |path|
192
+ # is a path relative to the dirname of |relative_to|, returns a path that
193
+ # identifies |path| relative to the current directory.
194
+ rel_dir = os.path.dirname(relative_to)
195
+ return os.path.normpath(os.path.join(rel_dir, path))
196
+
197
+
198
+ # re objects used by EncodePOSIXShellArgument. See IEEE 1003.1 XCU.2.2 at
199
+ # http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_02
200
+ # and the documentation for various shells.
201
+
202
+ # _quote is a pattern that should match any argument that needs to be quoted
203
+ # with double-quotes by EncodePOSIXShellArgument. It matches the following
204
+ # characters appearing anywhere in an argument:
205
+ # \t, \n, space parameter separators
206
+ # # comments
207
+ # $ expansions (quoted to always expand within one argument)
208
+ # % called out by IEEE 1003.1 XCU.2.2
209
+ # & job control
210
+ # ' quoting
211
+ # (, ) subshell execution
212
+ # *, ?, [ pathname expansion
213
+ # ; command delimiter
214
+ # <, >, | redirection
215
+ # = assignment
216
+ # {, } brace expansion (bash)
217
+ # ~ tilde expansion
218
+ # It also matches the empty string, because "" (or '') is the only way to
219
+ # represent an empty string literal argument to a POSIX shell.
220
+ #
221
+ # This does not match the characters in _escape, because those need to be
222
+ # backslash-escaped regardless of whether they appear in a double-quoted
223
+ # string.
224
+ _quote = re.compile('[\t\n #$%&\'()*;<=>?[{|}~]|^$')
225
+
226
+ # _escape is a pattern that should match any character that needs to be
227
+ # escaped with a backslash, whether or not the argument matched the _quote
228
+ # pattern. _escape is used with re.sub to backslash anything in _escape's
229
+ # first match group, hence the (parentheses) in the regular expression.
230
+ #
231
+ # _escape matches the following characters appearing anywhere in an argument:
232
+ # " to prevent POSIX shells from interpreting this character for quoting
233
+ # \ to prevent POSIX shells from interpreting this character for escaping
234
+ # ` to prevent POSIX shells from interpreting this character for command
235
+ # substitution
236
+ # Missing from this list is $, because the desired behavior of
237
+ # EncodePOSIXShellArgument is to permit parameter (variable) expansion.
238
+ #
239
+ # Also missing from this list is !, which bash will interpret as the history
240
+ # expansion character when history is enabled. bash does not enable history
241
+ # by default in non-interactive shells, so this is not thought to be a problem.
242
+ # ! was omitted from this list because bash interprets "\!" as a literal string
243
+ # including the backslash character (avoiding history expansion but retaining
244
+ # the backslash), which would not be correct for argument encoding. Handling
245
+ # this case properly would also be problematic because bash allows the history
246
+ # character to be changed with the histchars shell variable. Fortunately,
247
+ # as history is not enabled in non-interactive shells and
248
+ # EncodePOSIXShellArgument is only expected to encode for non-interactive
249
+ # shells, there is no room for error here by ignoring !.
250
+ _escape = re.compile(r'(["\\`])')
251
+
252
+ def EncodePOSIXShellArgument(argument):
253
+ """Encodes |argument| suitably for consumption by POSIX shells.
254
+
255
+ argument may be quoted and escaped as necessary to ensure that POSIX shells
256
+ treat the returned value as a literal representing the argument passed to
257
+ this function. Parameter (variable) expansions beginning with $ are allowed
258
+ to remain intact without escaping the $, to allow the argument to contain
259
+ references to variables to be expanded by the shell.
260
+ """
261
+
262
+ if not isinstance(argument, str):
263
+ argument = str(argument)
264
+
265
+ if _quote.search(argument):
266
+ quote = '"'
267
+ else:
268
+ quote = ''
269
+
270
+ encoded = quote + re.sub(_escape, r'\\\1', argument) + quote
271
+
272
+ return encoded
273
+
274
+
275
+ def EncodePOSIXShellList(list):
276
+ """Encodes |list| suitably for consumption by POSIX shells.
277
+
278
+ Returns EncodePOSIXShellArgument for each item in list, and joins them
279
+ together using the space character as an argument separator.
280
+ """
281
+
282
+ encoded_arguments = []
283
+ for argument in list:
284
+ encoded_arguments.append(EncodePOSIXShellArgument(argument))
285
+ return ' '.join(encoded_arguments)
286
+
287
+
288
+ def DeepDependencyTargets(target_dicts, roots):
289
+ """Returns the recursive list of target dependencies."""
290
+ dependencies = set()
291
+ pending = set(roots)
292
+ while pending:
293
+ # Pluck out one.
294
+ r = pending.pop()
295
+ # Skip if visited already.
296
+ if r in dependencies:
297
+ continue
298
+ # Add it.
299
+ dependencies.add(r)
300
+ # Add its children.
301
+ spec = target_dicts[r]
302
+ pending.update(set(spec.get('dependencies', [])))
303
+ pending.update(set(spec.get('dependencies_original', [])))
304
+ return list(dependencies - set(roots))
305
+
306
+
307
+ def BuildFileTargets(target_list, build_file):
308
+ """From a target_list, returns the subset from the specified build_file.
309
+ """
310
+ return [p for p in target_list if BuildFile(p) == build_file]
311
+
312
+
313
+ def AllTargets(target_list, target_dicts, build_file):
314
+ """Returns all targets (direct and dependencies) for the specified build_file.
315
+ """
316
+ bftargets = BuildFileTargets(target_list, build_file)
317
+ deptargets = DeepDependencyTargets(target_dicts, bftargets)
318
+ return bftargets + deptargets
319
+
320
+
321
+ def WriteOnDiff(filename):
322
+ """Write to a file only if the new contents differ.
323
+
324
+ Arguments:
325
+ filename: name of the file to potentially write to.
326
+ Returns:
327
+ A file like object which will write to temporary file and only overwrite
328
+ the target if it differs (on close).
329
+ """
330
+
331
+ class Writer:
332
+ """Wrapper around file which only covers the target if it differs."""
333
+ def __init__(self):
334
+ # Pick temporary file.
335
+ tmp_fd, self.tmp_path = tempfile.mkstemp(
336
+ suffix='.tmp',
337
+ prefix=os.path.split(filename)[1] + '.gyp.',
338
+ dir=os.path.split(filename)[0])
339
+ try:
340
+ self.tmp_file = os.fdopen(tmp_fd, 'wb')
341
+ except Exception:
342
+ # Don't leave turds behind.
343
+ os.unlink(self.tmp_path)
344
+ raise
345
+
346
+ def __getattr__(self, attrname):
347
+ # Delegate everything else to self.tmp_file
348
+ return getattr(self.tmp_file, attrname)
349
+
350
+ def close(self):
351
+ try:
352
+ # Close tmp file.
353
+ self.tmp_file.close()
354
+ # Determine if different.
355
+ same = False
356
+ try:
357
+ same = filecmp.cmp(self.tmp_path, filename, False)
358
+ except OSError, e:
359
+ if e.errno != errno.ENOENT:
360
+ raise
361
+
362
+ if same:
363
+ # The new file is identical to the old one, just get rid of the new
364
+ # one.
365
+ os.unlink(self.tmp_path)
366
+ else:
367
+ # The new file is different from the old one, or there is no old one.
368
+ # Rename the new file to the permanent name.
369
+ #
370
+ # tempfile.mkstemp uses an overly restrictive mode, resulting in a
371
+ # file that can only be read by the owner, regardless of the umask.
372
+ # There's no reason to not respect the umask here, which means that
373
+ # an extra hoop is required to fetch it and reset the new file's mode.
374
+ #
375
+ # No way to get the umask without setting a new one? Set a safe one
376
+ # and then set it back to the old value.
377
+ umask = os.umask(077)
378
+ os.umask(umask)
379
+ os.chmod(self.tmp_path, 0666 & ~umask)
380
+ if sys.platform == 'win32' and os.path.exists(filename):
381
+ # NOTE: on windows (but not cygwin) rename will not replace an
382
+ # existing file, so it must be preceded with a remove. Sadly there
383
+ # is no way to make the switch atomic.
384
+ os.remove(filename)
385
+ os.rename(self.tmp_path, filename)
386
+ except Exception:
387
+ # Don't leave turds behind.
388
+ os.unlink(self.tmp_path)
389
+ raise
390
+
391
+ return Writer()
392
+
393
+
394
+ def EnsureDirExists(path):
395
+ """Make sure the directory for |path| exists."""
396
+ try:
397
+ os.makedirs(os.path.dirname(path))
398
+ except OSError:
399
+ pass
400
+
401
+
402
+ def GetFlavor(params):
403
+ """Returns |params.flavor| if it's set, the system's default flavor else."""
404
+ flavors = {
405
+ 'cygwin': 'win',
406
+ 'win32': 'win',
407
+ 'darwin': 'mac',
408
+ }
409
+
410
+ if 'flavor' in params:
411
+ return params['flavor']
412
+ if sys.platform in flavors:
413
+ return flavors[sys.platform]
414
+ if sys.platform.startswith('sunos'):
415
+ return 'solaris'
416
+ if sys.platform.startswith('freebsd'):
417
+ return 'freebsd'
418
+ if sys.platform.startswith('openbsd'):
419
+ return 'openbsd'
420
+ if sys.platform.startswith('aix'):
421
+ return 'aix'
422
+
423
+ return 'linux'
424
+
425
+
426
+ def CopyTool(flavor, out_path):
427
+ """Finds (flock|mac|win)_tool.gyp in the gyp directory and copies it
428
+ to |out_path|."""
429
+ # aix and solaris just need flock emulation. mac and win use more complicated
430
+ # support scripts.
431
+ prefix = {
432
+ 'aix': 'flock',
433
+ 'solaris': 'flock',
434
+ 'mac': 'mac',
435
+ 'win': 'win'
436
+ }.get(flavor, None)
437
+ if not prefix:
438
+ return
439
+
440
+ # Slurp input file.
441
+ source_path = os.path.join(
442
+ os.path.dirname(os.path.abspath(__file__)), '%s_tool.py' % prefix)
443
+ with open(source_path) as source_file:
444
+ source = source_file.readlines()
445
+
446
+ # Add header and write it out.
447
+ tool_path = os.path.join(out_path, 'gyp-%s-tool' % prefix)
448
+ with open(tool_path, 'w') as tool_file:
449
+ tool_file.write(
450
+ ''.join([source[0], '# Generated by gyp. Do not edit.\n'] + source[1:]))
451
+
452
+ # Make file executable.
453
+ os.chmod(tool_path, 0755)
454
+
455
+
456
+ # From Alex Martelli,
457
+ # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52560
458
+ # ASPN: Python Cookbook: Remove duplicates from a sequence
459
+ # First comment, dated 2001/10/13.
460
+ # (Also in the printed Python Cookbook.)
461
+
462
+ def uniquer(seq, idfun=None):
463
+ if idfun is None:
464
+ idfun = lambda x: x
465
+ seen = {}
466
+ result = []
467
+ for item in seq:
468
+ marker = idfun(item)
469
+ if marker in seen: continue
470
+ seen[marker] = 1
471
+ result.append(item)
472
+ return result
473
+
474
+
475
+ class CycleError(Exception):
476
+ """An exception raised when an unexpected cycle is detected."""
477
+ def __init__(self, nodes):
478
+ self.nodes = nodes
479
+ def __str__(self):
480
+ return 'CycleError: cycle involving: ' + str(self.nodes)
481
+
482
+
483
+ def TopologicallySorted(graph, get_edges):
484
+ """Topologically sort based on a user provided edge definition.
485
+
486
+ Args:
487
+ graph: A list of node names.
488
+ get_edges: A function mapping from node name to a hashable collection
489
+ of node names which this node has outgoing edges to.
490
+ Returns:
491
+ A list containing all of the node in graph in topological order.
492
+ It is assumed that calling get_edges once for each node and caching is
493
+ cheaper than repeatedly calling get_edges.
494
+ Raises:
495
+ CycleError in the event of a cycle.
496
+ Example:
497
+ graph = {'a': '$(b) $(c)', 'b': 'hi', 'c': '$(b)'}
498
+ def GetEdges(node):
499
+ return re.findall(r'\$\(([^))]\)', graph[node])
500
+ print TopologicallySorted(graph.keys(), GetEdges)
501
+ ==>
502
+ ['a', 'c', b']
503
+ """
504
+ get_edges = memoize(get_edges)
505
+ visited = set()
506
+ visiting = set()
507
+ ordered_nodes = []
508
+ def Visit(node):
509
+ if node in visiting:
510
+ raise CycleError(visiting)
511
+ if node in visited:
512
+ return
513
+ visited.add(node)
514
+ visiting.add(node)
515
+ for neighbor in get_edges(node):
516
+ Visit(neighbor)
517
+ visiting.remove(node)
518
+ ordered_nodes.insert(0, node)
519
+ for node in sorted(graph):
520
+ Visit(node)
521
+ return ordered_nodes