-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 1093dad
Showing
18 changed files
with
992 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# Compiled | ||
# *.com | ||
*.class | ||
*.dll | ||
*.exe | ||
*.o | ||
*.so | ||
*.pyc | ||
*.pyo | ||
__pycache__/ | ||
*.egg-info/ | ||
|
||
# Backups | ||
*~ | ||
*.swp* | ||
*.kate-swp | ||
|
||
# Miscellaneous | ||
*.sqlite | ||
|
||
# OS generated files | ||
.directory | ||
.DS_Store | ||
.DS_Store? | ||
._* | ||
.Spotlight-V100/ | ||
.Trashes/ | ||
ehthumbs.db | ||
Thumbs.db | ||
|
||
# Bad files | ||
*,* | ||
.*.plist | ||
*.LSSharedFileList* | ||
.plist | ||
*crashlyt* | ||
/.mypy_cache/ | ||
/.vscode/.ropeproject/objectdb | ||
/.vscode/settings.json | ||
/Preferences/com.googlecode.iterm2.private.plist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
[settings] | ||
order_by_type=true | ||
combine_as_imports=true | ||
from_first=true | ||
case_sensitive=true | ||
use_parentheses=true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
# The default ``config.py`` | ||
# flake8: noqa | ||
from typing import Any, Dict | ||
|
||
|
||
def set_prefs(prefs: Dict[str, Any]) -> None: | ||
"""This function is called before opening the project""" | ||
|
||
# Specify which files and folders to ignore in the project. | ||
# Changes to ignored resources are not added to the history and | ||
# VCSs. Also they are not returned in `Project.get_files()`. | ||
# Note that ``?`` and ``*`` match all characters but slashes. | ||
# '*.pyc': matches 'test.pyc' and 'pkg/test.pyc' | ||
# 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc' | ||
# '.svn': matches 'pkg/.svn' and all of its children | ||
# 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o' | ||
# 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o' | ||
prefs['ignored_resources'] = [ | ||
'*.pyc', '*~', '.ropeproject', '.hg', '.svn', '_svn', '.git', '.tox' | ||
] | ||
|
||
# Specifies which files should be considered python files. It is | ||
# useful when you have scripts inside your project. Only files | ||
# ending with ``.py`` are considered to be python files by | ||
# default. | ||
# prefs['python_files'] = ['*.py'] | ||
|
||
# Custom source folders: By default rope searches the project | ||
# for finding source folders (folders that should be searched | ||
# for finding modules). You can add paths to that list. Note | ||
# that rope guesses project source folders correctly most of the | ||
# time; use this if you have any problems. | ||
# The folders should be relative to project root and use '/' for | ||
# separating folders regardless of the platform rope is running on. | ||
# 'src/my_source_folder' for instance. | ||
# prefs.add('source_folders', 'src') | ||
|
||
# You can extend python path for looking up modules | ||
# prefs.add('python_path', '~/python/') | ||
|
||
# Should rope save object information or not. | ||
prefs['save_objectdb'] = True | ||
prefs['compress_objectdb'] = False | ||
|
||
# If `True`, rope analyzes each module when it is being saved. | ||
prefs['automatic_soa'] = True | ||
# The depth of calls to follow in static object analysis | ||
prefs['soa_followed_calls'] = 0 | ||
|
||
# If `False` when running modules or unit tests "dynamic object | ||
# analysis" is turned off. This makes them much faster. | ||
prefs['perform_doa'] = True | ||
|
||
# Rope can check the validity of its object DB when running. | ||
prefs['validate_objectdb'] = True | ||
|
||
# How many undos to hold? | ||
prefs['max_history_items'] = 32 | ||
|
||
# Shows whether to save history across sessions. | ||
prefs['save_history'] = True | ||
prefs['compress_history'] = False | ||
|
||
# Set the number spaces used for indenting. According to | ||
# :PEP:`8`, it is best to use 4 spaces. Since most of rope's | ||
# unit-tests use 4 spaces it is more reliable, too. | ||
prefs['indent_size'] = 4 | ||
|
||
# Builtin and c-extension modules that are allowed to be imported | ||
# and inspected by rope. | ||
prefs['extension_modules'] = [] | ||
|
||
# Add all standard c-extensions to extension_modules list. | ||
prefs['import_dynload_stdmods'] = True | ||
|
||
# If `True` modules with syntax errors are considered to be empty. | ||
# The default value is `False`; When `False` syntax errors raise | ||
# `rope.base.exceptions.ModuleSyntaxError` exception. | ||
prefs['ignore_syntax_errors'] = False | ||
|
||
# If `True`, rope ignores unresolvable imports. Otherwise, they | ||
# appear in the importing namespace. | ||
prefs['ignore_bad_imports'] = False | ||
|
||
# If `True`, rope will insert new module imports as | ||
# `from <package> import <module>` by default. | ||
prefs['prefer_module_from_imports'] = False | ||
|
||
# If `True`, rope will transform a comma list of imports into | ||
# multiple separate import statements when organizing | ||
# imports. | ||
prefs['split_imports'] = False | ||
|
||
# If `True`, rope will remove all top-level import statements and | ||
# reinsert them at the top of the module when making changes. | ||
prefs['pull_imports_to_top'] = True | ||
|
||
# If `True`, rope will sort imports alphabetically by module name instead | ||
# of alphabetically by import statement, with from imports after normal | ||
# imports. | ||
prefs['sort_imports_alphabetically'] = False | ||
|
||
# Location of implementation of | ||
# rope.base.oi.type_hinting.interfaces.ITypeHintingFactory In general | ||
# case, you don't have to change this value, unless you're an rope expert. | ||
# Change this value to inject you own implementations of interfaces | ||
# listed in module rope.base.oi.type_hinting.providers.interfaces | ||
# For example, you can add you own providers for Django Models, or disable | ||
# the search type-hinting in a class hierarchy, etc. | ||
prefs['type_hinting_factory'] = ( | ||
'rope.base.oi.type_hinting.factory.default_type_hinting_factory') | ||
|
||
|
||
def project_opened(_project: Any) -> None: | ||
"""This function is called after opening the project""" | ||
# Do whatever you like here! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
{ | ||
"cSpell.ignorePaths": [ | ||
"**/lib/python*/**/*.py", | ||
"**/package-lock.json", | ||
"**/node_modules/**", | ||
"**/vscode-extension/**", | ||
"**/.git/objects/**", | ||
".vscode", | ||
"exec-defaults.sh", | ||
"Preferences/**" | ||
], | ||
"cSpell.ignoreWords": [ | ||
"asyncio", | ||
"crashlyt", | ||
"dynload", | ||
"ehthumbs", | ||
"googlecode", | ||
"iterm", | ||
"l", | ||
"macprefs", | ||
"mypy", | ||
"noqa", | ||
"objectdb", | ||
"plist", | ||
"plistlib", | ||
"plists", | ||
"plutil", | ||
"prefs", | ||
"pycache", | ||
"ropeproject", | ||
"sqlite", | ||
"strftime", | ||
"udvare", | ||
"undos", | ||
"unmatch", | ||
"vcss" | ||
], | ||
"python.linting.pylintEnabled": true, | ||
"python.pythonPath": "/opt/local/bin/python" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# macprefs | ||
|
||
Not documented yet. |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
#!/usr/bin/env python | ||
from os import listdir, makedirs | ||
from os.path import realpath | ||
from pathlib import Path | ||
from shlex import quote | ||
from typing import AsyncIterator, List, Optional, Tuple | ||
import argparse | ||
import asyncio | ||
import asyncio.subprocess as sp | ||
import plistlib | ||
import sys | ||
|
||
from .constants import (DOMAIN_SPLIT_DELIMITER, GLOBAL_DOMAIN_ARG, | ||
MAX_CONCURRENT_EXPORT_TASKS) | ||
from .filters import BAD_DOMAINS | ||
from .mp_typing import PlistRoot | ||
from .plist2defaults import plist_to_defaults_commands | ||
from .processing import remove_data_fields | ||
from .shell import delete_old_plists, git | ||
from .utils import setup_logging_stderr | ||
|
||
__all__ = ('main', ) | ||
|
||
|
||
async def _generate_domains(repo_prefs_dir: Path) -> AsyncIterator[str]: | ||
log = setup_logging_stderr(verbose=True) | ||
prefs_dir = Path.home().joinpath('Library/Preferences') | ||
can_skip: List[int] = [] | ||
p = await asyncio.create_subprocess_shell('defaults domains', | ||
stdout=sp.PIPE) | ||
deletions = [] | ||
assert p.stdout is not None | ||
domains_list = (await p.stdout.read()).decode('utf-8').replace( | ||
',', '').split(DOMAIN_SPLIT_DELIMITER) | ||
for i, domain in enumerate(domains_list): | ||
if (len(domain.strip()) == 0 | ||
or domain == '$(PRODUCT_BUNDLE_IDENTIFIER)'): | ||
deletions.append(domain) | ||
continue | ||
if not prefs_dir.joinpath(f'{domain}.plist').exists(): | ||
p = await asyncio.create_subprocess_shell( | ||
f'defaults read {quote(domain)}', | ||
stdout=sp.PIPE, | ||
stderr=sp.PIPE) | ||
await p.wait() | ||
if p.returncode != 0: | ||
candidates = list(prefs_dir.glob(f'{domain}*')) | ||
if not candidates: | ||
if i in can_skip: | ||
deletions.append(domain) | ||
continue | ||
assert p.stderr is not None | ||
if b'does not exist' in await p.stderr.read(): | ||
log.info('"%s" listed but does not exist', | ||
domain.strip()) | ||
deletions.append(domain) | ||
continue | ||
raise ValueError(f'Not sure how to handle {domain}') | ||
if len(candidates) != 1: | ||
candidates_joined = ', '.join(x.name for x in candidates) | ||
raise ValueError( | ||
f'Multiple candidates: {candidates_joined}') | ||
domain = candidates[0].stem | ||
can_skip.extend( | ||
range(i + 1, | ||
i + len(domain.split(DOMAIN_SPLIT_DELIMITER)))) | ||
yield domain | ||
else: | ||
yield domain | ||
yield GLOBAL_DOMAIN_ARG | ||
await delete_old_plists(deletions, repo_prefs_dir) | ||
|
||
|
||
async def _defaults_export( | ||
domain: str, repo_prefs_dir: Path) -> Tuple[str, Optional[PlistRoot]]: | ||
command = f'defaults export {quote(domain)}' | ||
out_domain = 'globalDomain' if domain == GLOBAL_DOMAIN_ARG else domain | ||
plist_out = repo_prefs_dir.joinpath(f'{out_domain}.plist') | ||
path_quoted = quote(str(plist_out)) | ||
command += f' {path_quoted}' | ||
log = setup_logging_stderr(verbose=True) | ||
log.debug('Running: %s', command) | ||
p = await asyncio.create_subprocess_shell(command, | ||
stdout=sp.PIPE, | ||
stderr=sp.PIPE) | ||
await p.wait() | ||
if p.returncode != 0: | ||
assert p.stderr is not None | ||
err = (await p.stderr.read()).decode('utf-8') | ||
raise RuntimeError( | ||
f'Non-zero exit status from defaults. STDERR: {err}') | ||
with plist_out.open('rb') as f: | ||
return domain, await remove_data_fields(plistlib.load(f)) | ||
|
||
|
||
async def _main(out_dir: str) -> int: | ||
out_dir = realpath(out_dir) | ||
try: | ||
makedirs(out_dir) | ||
except FileExistsError: | ||
pass | ||
repo_prefs_dir = Path(out_dir).joinpath('Preferences') | ||
|
||
export_tasks = [] | ||
all_data: List[Tuple[str, Optional[PlistRoot]]] = [] | ||
async for domain in _generate_domains(repo_prefs_dir): | ||
# spell-checker: disable | ||
if domain in ('com.apple.Music', 'com.apple.TV', | ||
'com.apple.identityservices.idstatuscache', | ||
'com.apple.security.KCN'): | ||
# spell-checker: enable | ||
continue | ||
export_tasks.append(_defaults_export(domain, repo_prefs_dir)) | ||
if len(export_tasks) == MAX_CONCURRENT_EXPORT_TASKS: | ||
all_data.extend(await asyncio.gather(*export_tasks)) | ||
export_tasks = [] | ||
all_data.extend(await asyncio.gather(*export_tasks)) | ||
|
||
exec_defaults = Path(out_dir).joinpath('exec-defaults.sh') | ||
tasks = [] | ||
known_domains = [] | ||
with exec_defaults.open('w+') as f: | ||
f.write('#!/usr/bin/env bash\n') | ||
f.write('# shellcheck disable=SC1112,SC2088,SC1010,SC2016,SC1003\n') | ||
f.write('# This file is generated, but is versioned.\n\n') | ||
for domain, root in sorted(all_data, key=lambda x: x[0]): | ||
if not root: | ||
continue | ||
async for line in plist_to_defaults_commands(domain, root): | ||
f.write(line + '\n') | ||
out_domain = ('globalDomain' | ||
if domain == GLOBAL_DOMAIN_ARG else domain) | ||
known_domains.append(out_domain) | ||
plist_path = repo_prefs_dir.joinpath(f'{out_domain}.plist') | ||
p = await asyncio.create_subprocess_shell( | ||
f'plutil -convert xml1 {quote(str(plist_path))}') | ||
tasks.append(p.wait()) | ||
|
||
await asyncio.wait(tasks) | ||
|
||
log = setup_logging_stderr(verbose=True) | ||
|
||
# Clean up very old plists | ||
delete_with_git = [ | ||
str(j[1]) | ||
for j in ((file, file_) | ||
for file, file_ in ((x, repo_prefs_dir.joinpath(x)) | ||
for x in listdir(str(repo_prefs_dir)) | ||
if x != '.gitignore') | ||
if file[:-6] not in known_domains and file_.exists() | ||
if not file_.is_dir()) | ||
] | ||
await git(['rm', '-f', '--ignore-unmatch', '--'] + delete_with_git, | ||
check=True) | ||
all_files = ' '.join(map(quote, delete_with_git)) | ||
cmd = f'rm -f -- {all_files}' | ||
log.debug('Executing: %s', cmd) | ||
p = await asyncio.create_subprocess_shell(f'rm -f -- {all_files}') | ||
await p.wait() | ||
|
||
delete_with_git_ = [] | ||
delete_with_rm = [] | ||
for x in BAD_DOMAINS: | ||
if x == 'MobileMeAccounts': | ||
continue | ||
plist = repo_prefs_dir.joinpath(f'{x}.plist') | ||
delete_with_git_.append(str(plist)) | ||
delete_with_rm.append(quote(str(plist))) | ||
await git(['rm', '-f', '--ignore-unmatch', '--'] + delete_with_git_, | ||
check=True) | ||
deletions = ' '.join(delete_with_rm) | ||
cmd = f'rm -f -- {deletions}' | ||
log.debug('Executing: %s', cmd) | ||
p = await asyncio.create_subprocess_shell(cmd) | ||
await p.wait() | ||
|
||
return 0 | ||
|
||
|
||
def main() -> int: | ||
"""Entry point.""" | ||
parser = argparse.ArgumentParser() | ||
parser.add_argument('-o', '--output-directory', default='.') | ||
args = parser.parse_args() | ||
return asyncio.run(_main(args.output_directory)) | ||
|
||
|
||
if __name__ == "__main__": | ||
sys.exit(main()) |
Oops, something went wrong.