Repository: xonsh/xonsh

Issue:
BOM trips 'xontrib load' when placed on the first line of 'rc.xsh'
## xonfig

<details>

```
$ xonfig

+------------------+-----------------+
| xonsh            | 0.9.27          |
| Python           | 3.9.2           |
| PLY              | 3.11            |
| have readline    | True            |
| prompt toolkit   | 3.0.16          |
| shell type       | prompt_toolkit  |
| pygments         | 2.8.0           |
| on posix         | True            |
| on linux         | True            |
| distro           | unknown         |
| on darwin        | False           |
| on windows       | False           |
| on cygwin        | False           |
| on msys2         | False           |
| is superuser     | False           |
| default encoding | utf-8           |
| xonsh encoding   | utf-8           |
| encoding errors  | surrogateescape |
| on jupyter       | False           |
| jupyter kernel   | None            |
| xontrib          | []              |
+------------------+-----------------+
```

</details>

## Expected Behavior
`xontrib load` should work even in files with a BOM 

## Current Behavior

For some reason some of my `.xsh` config files were encoded in UTF8 with a BOM and I've noticed a bug where if I place e.g. `xontrib load prompt_ret_code` on the first line I get an error.
When I move `xontrib load prompt_ret_code` to the second line (first line is now empty) or remove BOM from the file, everything loads just fine

### Traceback (if applicable)

<details>

```
traceback
Traceback (most recent call last):
  File "/home/linuxbrew/.linuxbrew/Cellar/xonsh/HEAD-b7b2c9a/libexec/lib/python3.9/site-packages/xonsh/__amalgam__.py", line 18071, in xonsh_script_run_control
    run_script_with_cache(filename, execer, ctx)
  File "/home/linuxbrew/.linuxbrew/Cellar/xonsh/HEAD-b7b2c9a/libexec/lib/python3.9/site-packages/xonsh/__amalgam__.py", line 3260, in run_script_with_cache
    ccode = compile_code(filename, code, execer, glb, loc, mode)
  File "/home/linuxbrew/.linuxbrew/Cellar/xonsh/HEAD-b7b2c9a/libexec/lib/python3.9/site-packages/xonsh/__amalgam__.py", line 3219, in compile_code
    ccode = execer.compile(code, glbs=glb, locs=loc, mode=mode, filename=filename)
  File "/home/linuxbrew/.linuxbrew/Cellar/xonsh/HEAD-b7b2c9a/libexec/lib/python3.9/site-packages/xonsh/__amalgam__.py", line 21588, in compile
    tree = self.parse(input, ctx, mode=mode, filename=filename, transform=transform)
  File "/home/linuxbrew/.linuxbrew/Cellar/xonsh/HEAD-b7b2c9a/libexec/lib/python3.9/site-packages/xonsh/__amalgam__.py", line 21548, in parse
    tree, input = self._parse_ctx_free(input, mode=mode, filename=filename)
  File "/home/linuxbrew/.linuxbrew/Cellar/xonsh/HEAD-b7b2c9a/libexec/lib/python3.9/site-packages/xonsh/__amalgam__.py", line 21691, in _parse_ctx_free
    raise original_error from None
  File "/home/linuxbrew/.linuxbrew/Cellar/xonsh/HEAD-b7b2c9a/libexec/lib/python3.9/site-packages/xonsh/__amalgam__.py", line 21672, in _parse_ctx_free
    tree = self.parser.parse(
  File "/home/linuxbrew/.linuxbrew/Cellar/xonsh/HEAD-b7b2c9a/libexec/lib/python3.9/site-packages/xonsh/parsers/base.py", line 506, in parse
    tree = self.parser.parse(input=s, lexer=self.lexer, debug=debug_level)
  File "/home/linuxbrew/.linuxbrew/Cellar/xonsh/HEAD-b7b2c9a/libexec/lib/python3.9/site-packages/xonsh/ply/ply/yacc.py", line 335, in parse
    return self.parseopt_notrack(input, lexer, debug, tracking, tokenfunc)
  File "/home/linuxbrew/.linuxbrew/Cellar/xonsh/HEAD-b7b2c9a/libexec/lib/python3.9/site-packages/xonsh/ply/ply/yacc.py", line 1203, in parseopt_notrack
    tok = call_errorfunc(self.errorfunc, errtoken, self)
  File "/home/linuxbrew/.linuxbrew/Cellar/xonsh/HEAD-b7b2c9a/libexec/lib/python3.9/site-packages/xonsh/ply/ply/yacc.py", line 194, in call_errorfunc
    r = errorfunc(token)
  File "/home/linuxbrew/.linuxbrew/Cellar/xonsh/HEAD-b7b2c9a/libexec/lib/python3.9/site-packages/xonsh/parsers/base.py", line 3453, in p_error
    self._parse_error(msg, self.currloc(lineno=p.lineno, column=p.lexpos))
  File "/home/linuxbrew/.linuxbrew/Cellar/xonsh/HEAD-b7b2c9a/libexec/lib/python3.9/site-packages/xonsh/parsers/base.py", line 648, in _parse_error
    raise err
SyntaxError: /home/es/.config/xonsh/rc.xsh:1:8: ('code: load',)
﻿xontrib load prompt_ret_code
        ^
syntax error in xonsh run control file '/home/es/.config/xonsh/rc.xsh': /home/es/.config/xonsh/rc.xsh:1:8: ('code: load',)
﻿xontrib load prompt_ret_code
```

</details>

## Steps to Reproduce
- Convert your config file `rc.xsh` to a UTF8 encoding with a BOM
- Add `xontrib load prompt_ret_code` on the first line of this file
- Launch `xonsh`

## For community
⬇️  **Please click the 👍 reaction instead of leaving a `+1` or 👍  comment**



Discussion:
Hi @eugenesvk ! Thank you for reporting.
The first steps could be from https://stackoverflow.com/questions/13590749/reading-unicode-file-data-with-bom-chars-in-python/
PR is welcome!

Resolving patch:
diff --git a/xonsh/imphooks.py b/xonsh/imphooks.py
--- a/xonsh/imphooks.py
+++ b/xonsh/imphooks.py
@@ -27,11 +27,15 @@ def ENCODING_LINE():
 
 
 def find_source_encoding(src):
-    """Finds the source encoding given bytes representing a file. If
-    no encoding is found, UTF-8 will be returned as per the docs
+    """Finds the source encoding given bytes representing a file by checking
+    a special comment at either the first or second line of the source file.
     https://docs.python.org/3/howto/unicode.html#unicode-literals-in-python-source-code
+    If no encoding is found, UTF-8 codec with BOM signature will be returned
+    as it skips an optional UTF-8 encoded BOM at the start of the data
+    and is otherwise the same as UTF-8
+    https://docs.python.org/3/library/codecs.html#module-encodings.utf_8_sig
     """
-    utf8 = "UTF-8"
+    utf8 = "utf-8-sig"
     first, _, rest = src.partition(b"\n")
     m = ENCODING_LINE.match(first)
     if m is not None:


Test patch: