We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Can't subst \$( in Textfile
**Describe the bug**
Generating .NET Core csproj files I needed to insert this string literal into the output
 `NETCore\bin\$(Platform)\$(Configuration)\`

But there does not seem to be any way to output the sequence `\$(`

To reproduce clone this:
   https://github.com/datalogics-robb/quote-test-scons

run `scons` to generate  GenFile.csproj and observe this string in the output:
`NETCore\bin\$Platform)\$Configuration)\`

SCons version 4.2.0
CPython 3.9.[67] tested  on Mac/Linux


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `SCons/Tool/textfile.py`
Content:
```
1 # -*- python -*-
2 #
3 # __COPYRIGHT__
4 #
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
12 #
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
15 #
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 #
24 
25 __doc__ = """
26 Textfile/Substfile builder for SCons.
27 
28     Create file 'target' which typically is a textfile.  The 'source'
29     may be any combination of strings, Nodes, or lists of same.  A
30     'linesep' will be put between any part written and defaults to
31     os.linesep.
32 
33     The only difference between the Textfile builder and the Substfile
34     builder is that strings are converted to Value() nodes for the
35     former and File() nodes for the latter.  To insert files in the
36     former or strings in the latter, wrap them in a File() or Value(),
37     respectively.
38 
39     The values of SUBST_DICT first have any construction variables
40     expanded (its keys are not expanded).  If a value of SUBST_DICT is
41     a python callable function, it is called and the result is expanded
42     as the value.  Values are substituted in a "random" order; if any
43     substitution could be further expanded by another substitution, it
44     is unpredictable whether the expansion will occur.
45 """
46 
47 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
48 
49 import SCons
50 
51 
52 from SCons.Node import Node
53 from SCons.Node.Python import Value
54 from SCons.Util import is_String, is_Sequence, is_Dict, to_bytes
55 
56 
57 TEXTFILE_FILE_WRITE_MODE = 'w'
58 
59 LINESEP = '\n'
60 
61 def _do_subst(node, subs):
62     """
63     Fetch the node contents and replace all instances of the keys with
64     their values.  For example, if subs is
65         {'%VERSION%': '1.2345', '%BASE%': 'MyProg', '%prefix%': '/bin'},
66     then all instances of %VERSION% in the file will be replaced with
67     1.2345 and so forth.
68     """
69     contents = node.get_text_contents()
70     if subs:
71         for (k, val) in subs:
72             contents = contents.replace(k, val)
73 
74     if 'b' in TEXTFILE_FILE_WRITE_MODE:
75         try:
76             contents = bytearray(contents, 'utf-8')
77         except TypeError:
78             # TODO: this should not happen, get_text_contents returns text
79             contents = bytearray(contents)
80 
81     return contents
82 
83 
84 def _action(target, source, env):
85 
86     # prepare the line separator
87     linesep = env['LINESEPARATOR']
88     if linesep is None:
89         linesep = LINESEP # os.linesep
90     elif is_String(linesep):
91         pass
92     elif isinstance(linesep, Value):
93         linesep = linesep.get_text_contents()
94     else:
95         raise SCons.Errors.UserError('unexpected type/class for LINESEPARATOR: %s'
96                                      % repr(linesep), None)
97 
98     if 'b' in TEXTFILE_FILE_WRITE_MODE:
99         linesep = to_bytes(linesep)
100 
101     # create a dictionary to use for the substitutions
102     if 'SUBST_DICT' not in env:
103         subs = None    # no substitutions
104     else:
105         subst_dict = env['SUBST_DICT']
106         if is_Dict(subst_dict):
107             subst_dict = list(subst_dict.items())
108         elif is_Sequence(subst_dict):
109             pass
110         else:
111             raise SCons.Errors.UserError('SUBST_DICT must be dict or sequence')
112         subs = []
113         for (k, value) in subst_dict:
114             if callable(value):
115                 value = value()
116             if is_String(value):
117                 value = env.subst(value)
118             else:
119                 value = str(value)
120             subs.append((k, value))
121 
122     # write the file
123     try:
124         target_file = open(target[0].get_path(), TEXTFILE_FILE_WRITE_MODE, newline='')
125     except (OSError, IOError) as e:
126         raise SCons.Errors.UserError("Can't write target file %s [%s]" % (target[0],e))
127 
128     # separate lines by 'linesep' only if linesep is not empty
129     lsep = None
130     for line in source:
131         if lsep:
132             target_file.write(lsep)
133 
134         target_file.write(_do_subst(line, subs))
135         lsep = linesep
136     target_file.close()
137 
138 
139 def _strfunc(target, source, env):
140     return "Creating '%s'" % target[0]
141 
142 
143 def _convert_list_R(newlist, sources):
144     for elem in sources:
145         if is_Sequence(elem):
146             _convert_list_R(newlist, elem)
147         elif isinstance(elem, Node):
148             newlist.append(elem)
149         else:
150             newlist.append(Value(elem))
151 
152 
153 def _convert_list(target, source, env):
154     if len(target) != 1:
155         raise SCons.Errors.UserError("Only one target file allowed")
156     newlist = []
157     _convert_list_R(newlist, source)
158     return target, newlist
159 
160 
161 _common_varlist = ['SUBST_DICT', 'LINESEPARATOR']
162 
163 _text_varlist = _common_varlist + ['TEXTFILEPREFIX', 'TEXTFILESUFFIX']
164 _text_builder = SCons.Builder.Builder(
165     action=SCons.Action.Action(_action, _strfunc, varlist=_text_varlist),
166     source_factory=Value,
167     emitter=_convert_list,
168     prefix='$TEXTFILEPREFIX',
169     suffix='$TEXTFILESUFFIX',
170 )
171 
172 _subst_varlist = _common_varlist + ['SUBSTFILEPREFIX', 'SUBSTFILESUFFIX']
173 _subst_builder = SCons.Builder.Builder(
174     action=SCons.Action.Action(_action, _strfunc, varlist=_subst_varlist),
175     source_factory=SCons.Node.FS.File,
176     emitter=_convert_list,
177     prefix='$SUBSTFILEPREFIX',
178     suffix='$SUBSTFILESUFFIX',
179     src_suffix=['.in'],
180 )
181 
182 
183 def generate(env):
184     env['LINESEPARATOR'] = LINESEP # os.linesep
185     env['BUILDERS']['Textfile'] = _text_builder
186     env['TEXTFILEPREFIX'] = ''
187     env['TEXTFILESUFFIX'] = '.txt'
188     env['BUILDERS']['Substfile'] = _subst_builder
189     env['SUBSTFILEPREFIX'] = ''
190     env['SUBSTFILESUFFIX'] = ''
191 
192 
193 def exists(env):
194     return 1
195 
196 # Local Variables:
197 # tab-width:4
198 # indent-tabs-mode:nil
199 # End:
200 # vim: set expandtab tabstop=4 shiftwidth=4:
201
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/SCons/Tool/textfile.py b/SCons/Tool/textfile.py
--- a/SCons/Tool/textfile.py
+++ b/SCons/Tool/textfile.py
@@ -1,6 +1,6 @@
-# -*- python -*-
+# MIT License
 #
-# __COPYRIGHT__
+# Copyright The SCons Foundation
 #
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
@@ -20,9 +20,8 @@
 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-#
 
-__doc__ = """
+"""
 Textfile/Substfile builder for SCons.
 
     Create file 'target' which typically is a textfile.  The 'source'
@@ -44,8 +43,6 @@
     is unpredictable whether the expansion will occur.
 """
 
-__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
-
 import SCons
 
 
@@ -58,6 +55,7 @@
 
 LINESEP = '\n'
 
+
 def _do_subst(node, subs):
     """
     Fetch the node contents and replace all instances of the keys with
@@ -86,7 +84,7 @@
     # prepare the line separator
     linesep = env['LINESEPARATOR']
     if linesep is None:
-        linesep = LINESEP # os.linesep
+        linesep = LINESEP  # os.linesep
     elif is_String(linesep):
         pass
     elif isinstance(linesep, Value):
@@ -114,7 +112,7 @@
             if callable(value):
                 value = value()
             if is_String(value):
-                value = env.subst(value)
+                value = env.subst(value, raw=1)
             else:
                 value = str(value)
             subs.append((k, value))

{"golden_diff": "diff --git a/SCons/Tool/textfile.py b/SCons/Tool/textfile.py\n--- a/SCons/Tool/textfile.py\n+++ b/SCons/Tool/textfile.py\n@@ -1,6 +1,6 @@\n-# -*- python -*-\n+# MIT License\n #\n-# __COPYRIGHT__\n+# Copyright The SCons Foundation\n #\n # Permission is hereby granted, free of charge, to any person obtaining\n # a copy of this software and associated documentation files (the\n@@ -20,9 +20,8 @@\n # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n-#\n \n-__doc__ = \"\"\"\n+\"\"\"\n Textfile/Substfile builder for SCons.\n \n     Create file 'target' which typically is a textfile.  The 'source'\n@@ -44,8 +43,6 @@\n     is unpredictable whether the expansion will occur.\n \"\"\"\n \n-__revision__ = \"__FILE__ __REVISION__ __DATE__ __DEVELOPER__\"\n-\n import SCons\n \n \n@@ -58,6 +55,7 @@\n \n LINESEP = '\\n'\n \n+\n def _do_subst(node, subs):\n     \"\"\"\n     Fetch the node contents and replace all instances of the keys with\n@@ -86,7 +84,7 @@\n     # prepare the line separator\n     linesep = env['LINESEPARATOR']\n     if linesep is None:\n-        linesep = LINESEP # os.linesep\n+        linesep = LINESEP  # os.linesep\n     elif is_String(linesep):\n         pass\n     elif isinstance(linesep, Value):\n@@ -114,7 +112,7 @@\n             if callable(value):\n                 value = value()\n             if is_String(value):\n-                value = env.subst(value)\n+                value = env.subst(value, raw=1)\n             else:\n                 value = str(value)\n             subs.append((k, value))\n", "issue": "Can't subst \\$( in Textfile\n**Describe the bug**\r\nGenerating .NET Core csproj files I needed to insert this string literal into the output\r\n `NETCore\\bin\\$(Platform)\\$(Configuration)\\`\r\n\r\nBut there does not seem to be any way to output the sequence `\\$(`\r\n\r\nTo reproduce clone this:\r\n   https://github.com/datalogics-robb/quote-test-scons\r\n\r\nrun `scons` to generate  GenFile.csproj and observe this string in the output:\r\n`NETCore\\bin\\$Platform)\\$Configuration)\\`\r\n\r\nSCons version 4.2.0\r\nCPython 3.9.[67] tested  on Mac/Linux\r\n\n", "before_files": [{"content": "# -*- python -*-\n#\n# __COPYRIGHT__\n#\n# Permission is hereby granted, free of charge, to any person obtaining\n# a copy of this software and associated documentation files (the\n# \"Software\"), to deal in the Software without restriction, including\n# without limitation the rights to use, copy, modify, merge, publish,\n# distribute, sublicense, and/or sell copies of the Software, and to\n# permit persons to whom the Software is furnished to do so, subject to\n# the following conditions:\n#\n# The above copyright notice and this permission notice shall be included\n# in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY\n# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE\n# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n#\n\n__doc__ = \"\"\"\nTextfile/Substfile builder for SCons.\n\n    Create file 'target' which typically is a textfile.  The 'source'\n    may be any combination of strings, Nodes, or lists of same.  A\n    'linesep' will be put between any part written and defaults to\n    os.linesep.\n\n    The only difference between the Textfile builder and the Substfile\n    builder is that strings are converted to Value() nodes for the\n    former and File() nodes for the latter.  To insert files in the\n    former or strings in the latter, wrap them in a File() or Value(),\n    respectively.\n\n    The values of SUBST_DICT first have any construction variables\n    expanded (its keys are not expanded).  If a value of SUBST_DICT is\n    a python callable function, it is called and the result is expanded\n    as the value.  Values are substituted in a \"random\" order; if any\n    substitution could be further expanded by another substitution, it\n    is unpredictable whether the expansion will occur.\n\"\"\"\n\n__revision__ = \"__FILE__ __REVISION__ __DATE__ __DEVELOPER__\"\n\nimport SCons\n\n\nfrom SCons.Node import Node\nfrom SCons.Node.Python import Value\nfrom SCons.Util import is_String, is_Sequence, is_Dict, to_bytes\n\n\nTEXTFILE_FILE_WRITE_MODE = 'w'\n\nLINESEP = '\\n'\n\ndef _do_subst(node, subs):\n    \"\"\"\n    Fetch the node contents and replace all instances of the keys with\n    their values.  For example, if subs is\n        {'%VERSION%': '1.2345', '%BASE%': 'MyProg', '%prefix%': '/bin'},\n    then all instances of %VERSION% in the file will be replaced with\n    1.2345 and so forth.\n    \"\"\"\n    contents = node.get_text_contents()\n    if subs:\n        for (k, val) in subs:\n            contents = contents.replace(k, val)\n\n    if 'b' in TEXTFILE_FILE_WRITE_MODE:\n        try:\n            contents = bytearray(contents, 'utf-8')\n        except TypeError:\n            # TODO: this should not happen, get_text_contents returns text\n            contents = bytearray(contents)\n\n    return contents\n\n\ndef _action(target, source, env):\n\n    # prepare the line separator\n    linesep = env['LINESEPARATOR']\n    if linesep is None:\n        linesep = LINESEP # os.linesep\n    elif is_String(linesep):\n        pass\n    elif isinstance(linesep, Value):\n        linesep = linesep.get_text_contents()\n    else:\n        raise SCons.Errors.UserError('unexpected type/class for LINESEPARATOR: %s'\n                                     % repr(linesep), None)\n\n    if 'b' in TEXTFILE_FILE_WRITE_MODE:\n        linesep = to_bytes(linesep)\n\n    # create a dictionary to use for the substitutions\n    if 'SUBST_DICT' not in env:\n        subs = None    # no substitutions\n    else:\n        subst_dict = env['SUBST_DICT']\n        if is_Dict(subst_dict):\n            subst_dict = list(subst_dict.items())\n        elif is_Sequence(subst_dict):\n            pass\n        else:\n            raise SCons.Errors.UserError('SUBST_DICT must be dict or sequence')\n        subs = []\n        for (k, value) in subst_dict:\n            if callable(value):\n                value = value()\n            if is_String(value):\n                value = env.subst(value)\n            else:\n                value = str(value)\n            subs.append((k, value))\n\n    # write the file\n    try:\n        target_file = open(target[0].get_path(), TEXTFILE_FILE_WRITE_MODE, newline='')\n    except (OSError, IOError) as e:\n        raise SCons.Errors.UserError(\"Can't write target file %s [%s]\" % (target[0],e))\n\n    # separate lines by 'linesep' only if linesep is not empty\n    lsep = None\n    for line in source:\n        if lsep:\n            target_file.write(lsep)\n\n        target_file.write(_do_subst(line, subs))\n        lsep = linesep\n    target_file.close()\n\n\ndef _strfunc(target, source, env):\n    return \"Creating '%s'\" % target[0]\n\n\ndef _convert_list_R(newlist, sources):\n    for elem in sources:\n        if is_Sequence(elem):\n            _convert_list_R(newlist, elem)\n        elif isinstance(elem, Node):\n            newlist.append(elem)\n        else:\n            newlist.append(Value(elem))\n\n\ndef _convert_list(target, source, env):\n    if len(target) != 1:\n        raise SCons.Errors.UserError(\"Only one target file allowed\")\n    newlist = []\n    _convert_list_R(newlist, source)\n    return target, newlist\n\n\n_common_varlist = ['SUBST_DICT', 'LINESEPARATOR']\n\n_text_varlist = _common_varlist + ['TEXTFILEPREFIX', 'TEXTFILESUFFIX']\n_text_builder = SCons.Builder.Builder(\n    action=SCons.Action.Action(_action, _strfunc, varlist=_text_varlist),\n    source_factory=Value,\n    emitter=_convert_list,\n    prefix='$TEXTFILEPREFIX',\n    suffix='$TEXTFILESUFFIX',\n)\n\n_subst_varlist = _common_varlist + ['SUBSTFILEPREFIX', 'SUBSTFILESUFFIX']\n_subst_builder = SCons.Builder.Builder(\n    action=SCons.Action.Action(_action, _strfunc, varlist=_subst_varlist),\n    source_factory=SCons.Node.FS.File,\n    emitter=_convert_list,\n    prefix='$SUBSTFILEPREFIX',\n    suffix='$SUBSTFILESUFFIX',\n    src_suffix=['.in'],\n)\n\n\ndef generate(env):\n    env['LINESEPARATOR'] = LINESEP # os.linesep\n    env['BUILDERS']['Textfile'] = _text_builder\n    env['TEXTFILEPREFIX'] = ''\n    env['TEXTFILESUFFIX'] = '.txt'\n    env['BUILDERS']['Substfile'] = _subst_builder\n    env['SUBSTFILEPREFIX'] = ''\n    env['SUBSTFILESUFFIX'] = ''\n\n\ndef exists(env):\n    return 1\n\n# Local Variables:\n# tab-width:4\n# indent-tabs-mode:nil\n# End:\n# vim: set expandtab tabstop=4 shiftwidth=4:\n", "path": "SCons/Tool/textfile.py"}], "after_files": [{"content": "# MIT License\n#\n# Copyright The SCons Foundation\n#\n# Permission is hereby granted, free of charge, to any person obtaining\n# a copy of this software and associated documentation files (the\n# \"Software\"), to deal in the Software without restriction, including\n# without limitation the rights to use, copy, modify, merge, publish,\n# distribute, sublicense, and/or sell copies of the Software, and to\n# permit persons to whom the Software is furnished to do so, subject to\n# the following conditions:\n#\n# The above copyright notice and this permission notice shall be included\n# in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY\n# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE\n# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\"\"\"\nTextfile/Substfile builder for SCons.\n\n    Create file 'target' which typically is a textfile.  The 'source'\n    may be any combination of strings, Nodes, or lists of same.  A\n    'linesep' will be put between any part written and defaults to\n    os.linesep.\n\n    The only difference between the Textfile builder and the Substfile\n    builder is that strings are converted to Value() nodes for the\n    former and File() nodes for the latter.  To insert files in the\n    former or strings in the latter, wrap them in a File() or Value(),\n    respectively.\n\n    The values of SUBST_DICT first have any construction variables\n    expanded (its keys are not expanded).  If a value of SUBST_DICT is\n    a python callable function, it is called and the result is expanded\n    as the value.  Values are substituted in a \"random\" order; if any\n    substitution could be further expanded by another substitution, it\n    is unpredictable whether the expansion will occur.\n\"\"\"\n\nimport SCons\n\n\nfrom SCons.Node import Node\nfrom SCons.Node.Python import Value\nfrom SCons.Util import is_String, is_Sequence, is_Dict, to_bytes\n\n\nTEXTFILE_FILE_WRITE_MODE = 'w'\n\nLINESEP = '\\n'\n\n\ndef _do_subst(node, subs):\n    \"\"\"\n    Fetch the node contents and replace all instances of the keys with\n    their values.  For example, if subs is\n        {'%VERSION%': '1.2345', '%BASE%': 'MyProg', '%prefix%': '/bin'},\n    then all instances of %VERSION% in the file will be replaced with\n    1.2345 and so forth.\n    \"\"\"\n    contents = node.get_text_contents()\n    if subs:\n        for (k, val) in subs:\n            contents = contents.replace(k, val)\n\n    if 'b' in TEXTFILE_FILE_WRITE_MODE:\n        try:\n            contents = bytearray(contents, 'utf-8')\n        except TypeError:\n            # TODO: this should not happen, get_text_contents returns text\n            contents = bytearray(contents)\n\n    return contents\n\n\ndef _action(target, source, env):\n\n    # prepare the line separator\n    linesep = env['LINESEPARATOR']\n    if linesep is None:\n        linesep = LINESEP  # os.linesep\n    elif is_String(linesep):\n        pass\n    elif isinstance(linesep, Value):\n        linesep = linesep.get_text_contents()\n    else:\n        raise SCons.Errors.UserError('unexpected type/class for LINESEPARATOR: %s'\n                                     % repr(linesep), None)\n\n    if 'b' in TEXTFILE_FILE_WRITE_MODE:\n        linesep = to_bytes(linesep)\n\n    # create a dictionary to use for the substitutions\n    if 'SUBST_DICT' not in env:\n        subs = None    # no substitutions\n    else:\n        subst_dict = env['SUBST_DICT']\n        if is_Dict(subst_dict):\n            subst_dict = list(subst_dict.items())\n        elif is_Sequence(subst_dict):\n            pass\n        else:\n            raise SCons.Errors.UserError('SUBST_DICT must be dict or sequence')\n        subs = []\n        for (k, value) in subst_dict:\n            if callable(value):\n                value = value()\n            if is_String(value):\n                value = env.subst(value, raw=1)\n            else:\n                value = str(value)\n            subs.append((k, value))\n\n    # write the file\n    try:\n        target_file = open(target[0].get_path(), TEXTFILE_FILE_WRITE_MODE, newline='')\n    except (OSError, IOError) as e:\n        raise SCons.Errors.UserError(\"Can't write target file %s [%s]\" % (target[0],e))\n\n    # separate lines by 'linesep' only if linesep is not empty\n    lsep = None\n    for line in source:\n        if lsep:\n            target_file.write(lsep)\n\n        target_file.write(_do_subst(line, subs))\n        lsep = linesep\n    target_file.close()\n\n\ndef _strfunc(target, source, env):\n    return \"Creating '%s'\" % target[0]\n\n\ndef _convert_list_R(newlist, sources):\n    for elem in sources:\n        if is_Sequence(elem):\n            _convert_list_R(newlist, elem)\n        elif isinstance(elem, Node):\n            newlist.append(elem)\n        else:\n            newlist.append(Value(elem))\n\n\ndef _convert_list(target, source, env):\n    if len(target) != 1:\n        raise SCons.Errors.UserError(\"Only one target file allowed\")\n    newlist = []\n    _convert_list_R(newlist, source)\n    return target, newlist\n\n\n_common_varlist = ['SUBST_DICT', 'LINESEPARATOR']\n\n_text_varlist = _common_varlist + ['TEXTFILEPREFIX', 'TEXTFILESUFFIX']\n_text_builder = SCons.Builder.Builder(\n    action=SCons.Action.Action(_action, _strfunc, varlist=_text_varlist),\n    source_factory=Value,\n    emitter=_convert_list,\n    prefix='$TEXTFILEPREFIX',\n    suffix='$TEXTFILESUFFIX',\n)\n\n_subst_varlist = _common_varlist + ['SUBSTFILEPREFIX', 'SUBSTFILESUFFIX']\n_subst_builder = SCons.Builder.Builder(\n    action=SCons.Action.Action(_action, _strfunc, varlist=_subst_varlist),\n    source_factory=SCons.Node.FS.File,\n    emitter=_convert_list,\n    prefix='$SUBSTFILEPREFIX',\n    suffix='$SUBSTFILESUFFIX',\n    src_suffix=['.in'],\n)\n\n\ndef generate(env):\n    env['LINESEPARATOR'] = LINESEP # os.linesep\n    env['BUILDERS']['Textfile'] = _text_builder\n    env['TEXTFILEPREFIX'] = ''\n    env['TEXTFILESUFFIX'] = '.txt'\n    env['BUILDERS']['Substfile'] = _subst_builder\n    env['SUBSTFILEPREFIX'] = ''\n    env['SUBSTFILESUFFIX'] = ''\n\n\ndef exists(env):\n    return 1\n\n# Local Variables:\n# tab-width:4\n# indent-tabs-mode:nil\n# End:\n# vim: set expandtab tabstop=4 shiftwidth=4:\n", "path": "SCons/Tool/textfile.py"}]}