redsnow 0.0.8

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 (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