Initial commit
This commit is contained in:
107
.gitignore
vendored
Normal file
107
.gitignore
vendored
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
# Created by .ignore support plugin (hsz.mobi)
|
||||||
|
### Python template
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# celery beat schedule file
|
||||||
|
celerybeat-schedule
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.idea/
|
||||||
7
Crx/__init__.py
Normal file
7
Crx/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from .connection import Connection
|
||||||
|
from .simplenode import SimpleNode
|
||||||
|
from .util import get_simple_con
|
||||||
|
|
||||||
|
__version__ = (1, 0, 0, 0)
|
||||||
|
|
||||||
|
__all__ = ["Connection", "SimpleNode", "get_simple_con"]
|
||||||
77
Crx/connection.py
Normal file
77
Crx/connection.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
from typing import Union
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
|
from .simplenode import SimpleNode
|
||||||
|
|
||||||
|
|
||||||
|
# TODO validation
|
||||||
|
|
||||||
|
"""
|
||||||
|
http://localhost:4502/crx/de/init.jsp?_dc=1549392939742
|
||||||
|
http://localhost:4502/crx/de/nodetypes.jsp?_dc=1549392939958
|
||||||
|
http://localhost:4502/crx/server/crx.default/jcr%3aroot/libs.1.json?_dc=1549392123434&node=xnode-265
|
||||||
|
http://localhost:4502/crx/de/query.jsp?_dc=1549392245191&_charset_=utf-8&type=xpath&stmt=%2Fjcr%3Aroot%2Fbin%2F%2F*%5Bjcr%3Acontains(.%2C%20%27asdf%27)%5D%20order%20by%20%40jcr%3Ascore&showResults=true
|
||||||
|
"""
|
||||||
|
|
||||||
|
CRX_SERVER_ROOT = '/crx/server/crx.default/jcr:root/'
|
||||||
|
CRX_QUERY = '/crx/de/query.jsp'
|
||||||
|
|
||||||
|
JSON_DATA_EXTENSION = '.1.json'
|
||||||
|
|
||||||
|
|
||||||
|
class CrxException(ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Connection:
|
||||||
|
def __init__(self,
|
||||||
|
host: str = 'localhost',
|
||||||
|
port: int = 4502,
|
||||||
|
protocol: str = 'http',
|
||||||
|
root: str = CRX_SERVER_ROOT,
|
||||||
|
query: str = CRX_QUERY):
|
||||||
|
|
||||||
|
self.host = f'{protocol}://{host}:{port}'
|
||||||
|
self.data_root = self.host + root
|
||||||
|
self.query_path = self.host + query
|
||||||
|
|
||||||
|
self.session = requests.session()
|
||||||
|
|
||||||
|
def login_basic(self, username: str, password: str):
|
||||||
|
self.session.auth = (username, password)
|
||||||
|
|
||||||
|
def get_node_raw(self, path: str):
|
||||||
|
url = urljoin(self.data_root, '.' + path + JSON_DATA_EXTENSION)
|
||||||
|
try:
|
||||||
|
response = self.session.get(url)
|
||||||
|
except requests.exceptions.RequestException as exception:
|
||||||
|
raise CrxException() # todo more specific exceptions
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = response.json()
|
||||||
|
except ValueError:
|
||||||
|
raise # todo
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get_simple_node(self, path: str) -> SimpleNode:
|
||||||
|
return SimpleNode(path, self.get_node_raw(path), self)
|
||||||
|
|
||||||
|
def rename_node(self, old_path: str, new_path: str):
|
||||||
|
diff = f'>{old_path} : {new_path}'
|
||||||
|
resp = self.session.post(self.data_root, data={':diff': diff})
|
||||||
|
resp.raise_for_status()
|
||||||
|
|
||||||
|
def apply_diff(self, diff: Union[str, bytes]):
|
||||||
|
files = {
|
||||||
|
':diff': (
|
||||||
|
None,
|
||||||
|
diff,
|
||||||
|
'text/plain; charset=utf-8'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
# todo check for exception
|
||||||
|
resp = self.session.post(self.data_root, files=files)
|
||||||
|
resp.raise_for_status()
|
||||||
95
Crx/node.py
Normal file
95
Crx/node.py
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
from copy import copy, deepcopy
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
def parse_iso_date(date: str) -> datetime:
|
||||||
|
return datetime.strptime(date, '%Y-%m-%dT%H:%M:%S.000%z')
|
||||||
|
|
||||||
|
|
||||||
|
def fmt_iso_date(date: datetime) -> str:
|
||||||
|
return date.strftime('%Y-%m-%dT%H:%M:%S.000%z')
|
||||||
|
|
||||||
|
|
||||||
|
PROPERTY_DEFAULT = {
|
||||||
|
int: 'LONG',
|
||||||
|
str: 'STRING',
|
||||||
|
float: 'DOUBLE',
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
type: [external, simple, complex]
|
||||||
|
content-type[complex]:
|
||||||
|
"""
|
||||||
|
|
||||||
|
PROPERTY_TYPES = {
|
||||||
|
'BINARY': {
|
||||||
|
'type': 'complex',
|
||||||
|
'content-type': 'jcr-value/binary'
|
||||||
|
},
|
||||||
|
'BOOLEAN': {
|
||||||
|
'type': 'simple',
|
||||||
|
'serialize': json.dumps
|
||||||
|
},
|
||||||
|
'DATE': {
|
||||||
|
'type': 'complex',
|
||||||
|
'content-type': 'jcr-value/date',
|
||||||
|
'serialize': fmt_iso_date,
|
||||||
|
'deserialize': parse_iso_date
|
||||||
|
},
|
||||||
|
'DECIMAL': {
|
||||||
|
'type': 'complex',
|
||||||
|
'content-type': 'jcr-value/decimal',
|
||||||
|
'serialize': str,
|
||||||
|
},
|
||||||
|
'DOUBLE': {
|
||||||
|
'type': 'simple',
|
||||||
|
'serialize': str,
|
||||||
|
},
|
||||||
|
'LONG': {
|
||||||
|
'type': 'simple',
|
||||||
|
'serialize': str,
|
||||||
|
},
|
||||||
|
'NAME': {},
|
||||||
|
'PATH': {},
|
||||||
|
'REFERENCE': {},
|
||||||
|
'STRING': {
|
||||||
|
'type': 'simple',
|
||||||
|
'serialize': json.dumps,
|
||||||
|
'deserialize': json.loads
|
||||||
|
},
|
||||||
|
'UNDEFINED': {},
|
||||||
|
'URI': {},
|
||||||
|
'WEAKREFERENCE': {},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Property:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Node:
|
||||||
|
def __init__(self, path: str, primary_type: str, is_new: bool = True, data: dict = None):
|
||||||
|
# TODO validate path and type
|
||||||
|
self.path = path
|
||||||
|
self.data = data or {}
|
||||||
|
self.data['jcr:primaryType'] = primary_type
|
||||||
|
self.original_path = copy(path)
|
||||||
|
self.original_data = deepcopy(data)
|
||||||
|
self.is_new = is_new
|
||||||
|
|
||||||
|
def _build_diff(self) -> str:
|
||||||
|
if self.is_new:
|
||||||
|
return self._build_diff_new()
|
||||||
|
else:
|
||||||
|
return self._build_diff_change()
|
||||||
|
|
||||||
|
def _build_diff_new(self) -> str:
|
||||||
|
data = []
|
||||||
|
ptype_dict = {'jcr:primaryType': self.data['jcr:primaryType']}
|
||||||
|
data.append(f'+{self.path} : {json.dumps(ptype_dict)}')
|
||||||
|
for key, value in self.data:
|
||||||
|
if key == 'jcr:primaryType':
|
||||||
|
continue # primaryType is handled differently
|
||||||
|
data.append(f'^{self.path}/{key} : {json.dumps(value)}')
|
||||||
|
return '\n'.join(data) + '\n'
|
||||||
30
Crx/simplenode.py
Normal file
30
Crx/simplenode.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
if False:
|
||||||
|
from .connection import Connection
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleNode:
|
||||||
|
def __init__(self, path: str, data: dict, connection: 'Connection'):
|
||||||
|
self.path = path
|
||||||
|
self._data = data
|
||||||
|
self._connection = connection
|
||||||
|
|
||||||
|
def __getitem__(self, item):
|
||||||
|
return getattr(self, item)
|
||||||
|
|
||||||
|
def __getattr__(self, item: str):
|
||||||
|
try:
|
||||||
|
return super(SimpleNode, self).__getattr__(item)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
value = self._data.get(item)
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError()
|
||||||
|
|
||||||
|
if isinstance(value, dict):
|
||||||
|
return self._connection.get_simple_node(self.path + '/' + item)
|
||||||
|
return value
|
||||||
|
|
||||||
|
def __dir__(self):
|
||||||
|
return super(SimpleNode, self).__dir__() + list(self._data.keys())
|
||||||
7
Crx/util.py
Normal file
7
Crx/util.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from .connection import Connection
|
||||||
|
|
||||||
|
|
||||||
|
def get_simple_con():
|
||||||
|
con = Connection()
|
||||||
|
con.login_basic('admin', 'admin')
|
||||||
|
return con
|
||||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
requests >= 2.21.0
|
||||||
Reference in New Issue
Block a user