omlish 0.0.0.dev221__py3-none-any.whl → 0.0.0.dev223__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. omlish/__about__.py +4 -4
  2. omlish/asyncs/asyncio/subprocesses.py +15 -23
  3. omlish/dynamic.py +1 -1
  4. omlish/funcs/genmachine.py +1 -1
  5. omlish/http/coro/fdio.py +3 -3
  6. omlish/http/coro/server.py +1 -1
  7. omlish/http/handlers.py +14 -0
  8. omlish/http/parsing.py +13 -0
  9. omlish/lite/marshal.py +22 -6
  10. omlish/specs/irc/messages/base.py +49 -0
  11. omlish/specs/irc/messages/formats.py +92 -0
  12. omlish/specs/irc/messages/messages.py +774 -0
  13. omlish/specs/irc/messages/parsing.py +98 -0
  14. omlish/specs/irc/numerics/numerics.py +57 -0
  15. omlish/subprocesses.py +88 -0
  16. {omlish-0.0.0.dev221.dist-info → omlish-0.0.0.dev223.dist-info}/METADATA +5 -5
  17. {omlish-0.0.0.dev221.dist-info → omlish-0.0.0.dev223.dist-info}/RECORD +32 -34
  18. omlish/docker/oci/building.py +0 -122
  19. omlish/docker/oci/data.py +0 -125
  20. omlish/docker/oci/datarefs.py +0 -98
  21. omlish/docker/oci/loading.py +0 -120
  22. omlish/docker/oci/media.py +0 -154
  23. omlish/docker/oci/repositories.py +0 -72
  24. /omlish/{docker/oci → specs/irc/messages}/__init__.py +0 -0
  25. /omlish/specs/irc/{format → protocol}/LICENSE +0 -0
  26. /omlish/specs/irc/{format → protocol}/__init__.py +0 -0
  27. /omlish/specs/irc/{format → protocol}/consts.py +0 -0
  28. /omlish/specs/irc/{format → protocol}/errors.py +0 -0
  29. /omlish/specs/irc/{format → protocol}/message.py +0 -0
  30. /omlish/specs/irc/{format → protocol}/nuh.py +0 -0
  31. /omlish/specs/irc/{format → protocol}/parsing.py +0 -0
  32. /omlish/specs/irc/{format → protocol}/rendering.py +0 -0
  33. /omlish/specs/irc/{format → protocol}/tags.py +0 -0
  34. /omlish/specs/irc/{format → protocol}/utils.py +0 -0
  35. {omlish-0.0.0.dev221.dist-info → omlish-0.0.0.dev223.dist-info}/LICENSE +0 -0
  36. {omlish-0.0.0.dev221.dist-info → omlish-0.0.0.dev223.dist-info}/WHEEL +0 -0
  37. {omlish-0.0.0.dev221.dist-info → omlish-0.0.0.dev223.dist-info}/entry_points.txt +0 -0
  38. {omlish-0.0.0.dev221.dist-info → omlish-0.0.0.dev223.dist-info}/top_level.txt +0 -0
omlish/docker/oci/data.py DELETED
@@ -1,125 +0,0 @@
1
- # ruff: noqa: UP006 UP007
2
- # @omlish-lite
3
- import abc
4
- import dataclasses as dc
5
- import enum
6
- import typing as ta
7
-
8
- from ...lite.marshal import OBJ_MARSHALER_FIELD_KEY
9
- from ...lite.marshal import OBJ_MARSHALER_OMIT_IF_NONE
10
- from .datarefs import OciDataRef
11
-
12
-
13
- ##
14
-
15
-
16
- @dc.dataclass()
17
- class OciDataclass(abc.ABC): # noqa
18
- pass
19
-
20
-
21
- ##
22
-
23
-
24
- @dc.dataclass()
25
- class OciImageIndex(OciDataclass):
26
- manifests: ta.List[ta.Union['OciImageIndex', 'OciImageManifest']]
27
-
28
- annotations: ta.Optional[ta.Dict[str, str]] = None
29
-
30
-
31
- #
32
-
33
-
34
- @dc.dataclass()
35
- class OciImageManifest(OciDataclass):
36
- config: 'OciImageConfig'
37
-
38
- layers: ta.List['OciImageLayer']
39
-
40
-
41
- #
42
-
43
-
44
- @dc.dataclass()
45
- class OciImageLayer(OciDataclass):
46
- class Kind(enum.Enum):
47
- TAR = enum.auto()
48
- TAR_GZIP = enum.auto()
49
- TAR_ZSTD = enum.auto()
50
-
51
- kind: Kind
52
-
53
- data: OciDataRef
54
-
55
-
56
- #
57
-
58
-
59
- @dc.dataclass()
60
- class OciImageConfig(OciDataclass):
61
- """https://github.com/opencontainers/image-spec/blob/92353b0bee778725c617e7d57317b568a7796bd0/config.md"""
62
-
63
- architecture: str
64
- os: str
65
-
66
- @dc.dataclass()
67
- class RootFs:
68
- type: str
69
- diff_ids: ta.List[str]
70
-
71
- rootfs: RootFs
72
-
73
- #
74
-
75
- created: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
76
- author: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
77
- os_version: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_FIELD_KEY: 'os.version', OBJ_MARSHALER_OMIT_IF_NONE: True}) # noqa
78
- os_features: ta.Optional[ta.List[str]] = dc.field(default=None, metadata={OBJ_MARSHALER_FIELD_KEY: 'os.features', OBJ_MARSHALER_OMIT_IF_NONE: True}) # noqa
79
- variant: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
80
-
81
- """
82
- config object, OPTIONAL
83
- User string, OPTIONAL
84
- ExposedPorts object, OPTIONAL
85
- Env array of strings, OPTIONAL
86
- Entrypoint array of strings, OPTIONAL
87
- Cmd array of strings, OPTIONAL
88
- Volumes object, OPTIONAL
89
- WorkingDir string, OPTIONAL
90
- Labels object, OPTIONAL
91
- StopSignal string, OPTIONAL
92
- ArgsEscaped boolean, OPTIONAL
93
- Memory integer, OPTIONAL
94
- MemorySwap integer, OPTIONAL
95
- CpuShares integer, OPTIONAL
96
- Healthcheck object, OPTIONAL
97
- """
98
- config: ta.Optional[ta.Dict[str, ta.Any]] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
99
-
100
- @dc.dataclass()
101
- class History:
102
- created: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
103
- author: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
104
- created_by: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
105
- comment: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
106
- empty_layer: ta.Optional[bool] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
107
-
108
- history: ta.Optional[ta.List[History]] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
109
-
110
-
111
- ##
112
-
113
-
114
- def is_empty_oci_dataclass(obj: OciDataclass) -> bool:
115
- if not isinstance(obj, OciDataclass):
116
- raise TypeError(obj)
117
-
118
- elif isinstance(obj, OciImageIndex):
119
- return not obj.manifests
120
-
121
- elif isinstance(obj, OciImageManifest):
122
- return not obj.layers
123
-
124
- else:
125
- return False
@@ -1,98 +0,0 @@
1
- # ruff: noqa: UP006 UP007
2
- # @omlish-lite
3
- import abc
4
- import dataclasses as dc
5
- import hashlib
6
- import io
7
- import os.path
8
- import shutil
9
- import typing as ta
10
-
11
- from omlish.lite.cached import cached_nullary
12
-
13
-
14
- ##
15
-
16
-
17
- @dc.dataclass(frozen=True)
18
- class OciDataRef(abc.ABC): # noqa
19
- pass
20
-
21
-
22
- @dc.dataclass(frozen=True)
23
- class BytesOciDataRef(OciDataRef):
24
- data: bytes
25
-
26
-
27
- @dc.dataclass(frozen=True)
28
- class FileOciDataRef(OciDataRef):
29
- path: str
30
-
31
-
32
- ##
33
-
34
-
35
- @dc.dataclass(frozen=True)
36
- class OciDataRefInfo:
37
- data: OciDataRef
38
-
39
- @cached_nullary
40
- def sha256(self) -> str:
41
- if isinstance(self.data, FileOciDataRef):
42
- with open(self.data.path, 'rb') as f:
43
- return hashlib.file_digest(f, 'sha256').hexdigest() # noqa
44
-
45
- elif isinstance(self.data, BytesOciDataRef):
46
- return hashlib.sha256(self.data.data).hexdigest()
47
-
48
- else:
49
- raise TypeError(self.data)
50
-
51
- @cached_nullary
52
- def digest(self) -> str:
53
- return f'sha256:{self.sha256()}'
54
-
55
- @cached_nullary
56
- def size(self) -> int:
57
- if isinstance(self.data, FileOciDataRef):
58
- return os.path.getsize(self.data.path)
59
-
60
- elif isinstance(self.data, BytesOciDataRef):
61
- return len(self.data.data)
62
-
63
- else:
64
- raise TypeError(self.data)
65
-
66
-
67
- def write_oci_data_ref_to_file(
68
- data: OciDataRef,
69
- dst: str,
70
- *,
71
- symlink: bool = False,
72
- ) -> None:
73
- if isinstance(data, FileOciDataRef):
74
- if symlink:
75
- os.symlink(
76
- os.path.relpath(data.path, os.path.dirname(dst)),
77
- dst,
78
- )
79
- else:
80
- shutil.copyfile(data.path, dst)
81
-
82
- elif isinstance(data, BytesOciDataRef):
83
- with open(dst, 'wb') as f:
84
- f.write(data.data)
85
-
86
- else:
87
- raise TypeError(data)
88
-
89
-
90
- def open_oci_data_ref(data: OciDataRef) -> ta.BinaryIO:
91
- if isinstance(data, FileOciDataRef):
92
- return open(data.path, 'rb')
93
-
94
- elif isinstance(data, BytesOciDataRef):
95
- return io.BytesIO(data.data)
96
-
97
- else:
98
- raise TypeError(data)
@@ -1,120 +0,0 @@
1
- # ruff: noqa: UP006 UP007
2
- # @omlish-lite
3
- import dataclasses as dc
4
- import json
5
- import typing as ta
6
-
7
- from ...lite.check import check
8
- from .data import OciImageConfig
9
- from .data import OciImageIndex
10
- from .data import OciImageLayer
11
- from .data import OciImageManifest
12
- from .data import is_empty_oci_dataclass
13
- from .media import OCI_IMAGE_LAYER_KIND_MEDIA_TYPES_
14
- from .media import OCI_MEDIA_FIELDS
15
- from .media import OciMediaDescriptor
16
- from .media import OciMediaImageConfig
17
- from .media import OciMediaImageIndex
18
- from .media import OciMediaImageManifest
19
- from .media import unmarshal_oci_media_dataclass
20
- from .repositories import OciRepository
21
-
22
-
23
- T = ta.TypeVar('T')
24
-
25
-
26
- ##
27
-
28
-
29
- class OciRepositoryLoader:
30
- def __init__(
31
- self,
32
- repo: OciRepository,
33
- ) -> None:
34
- super().__init__()
35
-
36
- self._repo = repo
37
-
38
- #
39
-
40
- def load_object(
41
- self,
42
- data: bytes,
43
- cls: ta.Type[T] = object, # type: ignore[assignment]
44
- *,
45
- media_type: ta.Optional[str] = None,
46
- ) -> T:
47
- text = data.decode('utf-8')
48
- dct = json.loads(text)
49
- obj = unmarshal_oci_media_dataclass(
50
- dct,
51
- media_type=media_type,
52
- )
53
- return check.isinstance(obj, cls)
54
-
55
- def read_object(
56
- self,
57
- digest: str,
58
- cls: ta.Type[T] = object, # type: ignore[assignment]
59
- *,
60
- media_type: ta.Optional[str] = None,
61
- ) -> T:
62
- data = self._repo.read_blob(digest)
63
- return self.load_object(
64
- data,
65
- cls,
66
- media_type=media_type,
67
- )
68
-
69
- def read_descriptor(
70
- self,
71
- desc: OciMediaDescriptor,
72
- cls: ta.Type[T] = object, # type: ignore[assignment]
73
- ) -> ta.Any:
74
- return self.read_object(
75
- desc.digest,
76
- cls,
77
- media_type=desc.media_type,
78
- )
79
-
80
- #
81
-
82
- def from_media(self, obj: ta.Any) -> ta.Any:
83
- def make_kw(*exclude):
84
- return {
85
- a: getattr(obj, a)
86
- for f in dc.fields(obj)
87
- if (a := f.name) not in OCI_MEDIA_FIELDS
88
- and a not in exclude
89
- }
90
-
91
- if isinstance(obj, OciMediaImageConfig):
92
- return OciImageConfig(**make_kw())
93
-
94
- elif isinstance(obj, OciMediaImageManifest):
95
- return OciImageManifest(
96
- **make_kw('config', 'layers'),
97
- config=self.from_media(self.read_descriptor(obj.config)),
98
- layers=[
99
- OciImageLayer(
100
- kind=lk,
101
- data=self._repo.ref_blob(l.digest),
102
- )
103
- for l in obj.layers
104
- if (lk := OCI_IMAGE_LAYER_KIND_MEDIA_TYPES_.get(l.media_type)) is not None
105
- ],
106
- )
107
-
108
- elif isinstance(obj, OciMediaImageIndex):
109
- return OciImageIndex(
110
- **make_kw('manifests'),
111
- manifests=[
112
- fm
113
- for m in obj.manifests
114
- for fm in [self.from_media(self.read_descriptor(m))]
115
- if not is_empty_oci_dataclass(fm)
116
- ],
117
- )
118
-
119
- else:
120
- raise TypeError(obj)
@@ -1,154 +0,0 @@
1
- # ruff: noqa: UP006 UP007
2
- # @omlish-lite
3
- import abc
4
- import dataclasses as dc
5
- import typing as ta
6
-
7
- from ...lite.check import check
8
- from ...lite.marshal import OBJ_MARSHALER_FIELD_KEY
9
- from ...lite.marshal import OBJ_MARSHALER_OMIT_IF_NONE
10
- from ...lite.marshal import unmarshal_obj
11
- from .data import OciImageConfig
12
- from .data import OciImageLayer
13
-
14
-
15
- ##
16
-
17
-
18
- OCI_MEDIA_FIELDS: ta.Collection[str] = frozenset([
19
- 'schema_version',
20
- 'media_type',
21
- ])
22
-
23
-
24
- @dc.dataclass()
25
- class OciMediaDataclass(abc.ABC): # noqa
26
- SCHEMA_VERSION: ta.ClassVar[int]
27
- MEDIA_TYPE: ta.ClassVar[str]
28
-
29
- def __init_subclass__(cls, **kwargs: ta.Any) -> None:
30
- super().__init_subclass__(**kwargs)
31
- for a in OCI_MEDIA_FIELDS:
32
- check.in_(a, cls.__dict__)
33
-
34
-
35
- _REGISTERED_OCI_MEDIA_DATACLASSES: ta.Dict[str, ta.Type[OciMediaDataclass]] = {}
36
-
37
-
38
- def _register_oci_media_dataclass(cls):
39
- check.issubclass(cls, OciMediaDataclass)
40
- check.arg(dc.is_dataclass(cls))
41
- mt = check.non_empty_str(cls.__dict__['MEDIA_TYPE'])
42
- check.not_in(mt, _REGISTERED_OCI_MEDIA_DATACLASSES)
43
- _REGISTERED_OCI_MEDIA_DATACLASSES[mt] = cls
44
- return cls
45
-
46
-
47
- def get_registered_oci_media_dataclass(media_type: str) -> ta.Optional[ta.Type[OciMediaDataclass]]:
48
- return _REGISTERED_OCI_MEDIA_DATACLASSES.get(media_type)
49
-
50
-
51
- def unmarshal_oci_media_dataclass(
52
- dct: ta.Mapping[str, ta.Any],
53
- *,
54
- media_type: ta.Optional[str] = None,
55
- ) -> ta.Any:
56
- if media_type is None:
57
- media_type = check.non_empty_str(dct['mediaType'])
58
- cls = _REGISTERED_OCI_MEDIA_DATACLASSES[media_type]
59
- return unmarshal_obj(dct, cls)
60
-
61
-
62
- ##
63
-
64
-
65
- @dc.dataclass()
66
- class OciMediaDescriptor:
67
- """https://github.com/opencontainers/image-spec/blob/92353b0bee778725c617e7d57317b568a7796bd0/descriptor.md#properties""" # noqa
68
-
69
- media_type: str = dc.field(metadata={OBJ_MARSHALER_FIELD_KEY: 'mediaType'})
70
- digest: str
71
- size: int
72
-
73
- #
74
-
75
- urls: ta.Optional[ta.Sequence[str]] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
76
- annotations: ta.Optional[ta.Mapping[str, str]] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True}) # noqa
77
- data: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
78
- artifact_type: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_FIELD_KEY: 'artifactType', OBJ_MARSHALER_OMIT_IF_NONE: True}) # noqa
79
-
80
- #
81
-
82
- platform: ta.Optional[ta.Mapping[str, ta.Any]] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True}) # noqa
83
-
84
-
85
- ##
86
-
87
-
88
- @_register_oci_media_dataclass
89
- @dc.dataclass()
90
- class OciMediaImageIndex(OciMediaDataclass):
91
- """https://github.com/opencontainers/image-spec/blob/92353b0bee778725c617e7d57317b568a7796bd0/image-index.md"""
92
-
93
- manifests: ta.Sequence[OciMediaDescriptor] # -> OciMediaImageIndex | OciMediaImageManifest
94
-
95
- #
96
-
97
- annotations: ta.Optional[ta.Mapping[str, str]] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True}) # noqa
98
-
99
- #
100
-
101
- SCHEMA_VERSION: ta.ClassVar[int] = 2
102
- schema_version: int = dc.field(default=SCHEMA_VERSION, metadata={OBJ_MARSHALER_FIELD_KEY: 'schemaVersion'})
103
-
104
- MEDIA_TYPE: ta.ClassVar[str] = 'application/vnd.oci.image.index.v1+json'
105
- media_type: str = dc.field(default=MEDIA_TYPE, metadata={OBJ_MARSHALER_FIELD_KEY: 'mediaType'})
106
-
107
-
108
- #
109
-
110
-
111
- @_register_oci_media_dataclass
112
- @dc.dataclass()
113
- class OciMediaImageManifest(OciMediaDataclass):
114
- """https://github.com/opencontainers/image-spec/blob/92353b0bee778725c617e7d57317b568a7796bd0/manifest.md"""
115
-
116
- config: OciMediaDescriptor # -> OciMediaImageConfig
117
-
118
- layers: ta.Sequence[OciMediaDescriptor]
119
-
120
- #
121
-
122
- SCHEMA_VERSION: ta.ClassVar[int] = 2
123
- schema_version: int = dc.field(default=SCHEMA_VERSION, metadata={OBJ_MARSHALER_FIELD_KEY: 'schemaVersion'})
124
-
125
- MEDIA_TYPE: ta.ClassVar[str] = 'application/vnd.oci.image.manifest.v1+json'
126
- media_type: str = dc.field(default=MEDIA_TYPE, metadata={OBJ_MARSHALER_FIELD_KEY: 'mediaType'})
127
-
128
-
129
- #
130
-
131
-
132
- OCI_IMAGE_LAYER_KIND_MEDIA_TYPES: ta.Mapping[OciImageLayer.Kind, str] = {
133
- OciImageLayer.Kind.TAR: 'application/vnd.oci.image.layer.v1.tar',
134
- OciImageLayer.Kind.TAR_GZIP: 'application/vnd.oci.image.layer.v1.tar+gzip',
135
- OciImageLayer.Kind.TAR_ZSTD: 'application/vnd.oci.image.layer.v1.tar+zstd',
136
- }
137
-
138
- OCI_IMAGE_LAYER_KIND_MEDIA_TYPES_: ta.Mapping[str, OciImageLayer.Kind] = {
139
- v: k
140
- for k, v in OCI_IMAGE_LAYER_KIND_MEDIA_TYPES.items()
141
- }
142
-
143
-
144
- #
145
-
146
-
147
- @_register_oci_media_dataclass
148
- @dc.dataclass()
149
- class OciMediaImageConfig(OciImageConfig, OciMediaDataclass):
150
- SCHEMA_VERSION: ta.ClassVar[int] = 2
151
- schema_version: int = dc.field(default=SCHEMA_VERSION, metadata={OBJ_MARSHALER_FIELD_KEY: 'schemaVersion'})
152
-
153
- MEDIA_TYPE: ta.ClassVar[str] = 'application/vnd.oci.image.config.v1+json'
154
- media_type: str = dc.field(default=MEDIA_TYPE, metadata={OBJ_MARSHALER_FIELD_KEY: 'mediaType'})
@@ -1,72 +0,0 @@
1
- # ruff: noqa: UP006 UP007
2
- # @omlish-lite
3
- import abc
4
- import os.path
5
- import typing as ta
6
-
7
- from ...lite.check import check
8
- from ...os.paths import is_path_in_dir
9
- from .datarefs import BytesOciDataRef
10
- from .datarefs import FileOciDataRef
11
- from .datarefs import OciDataRef
12
-
13
-
14
- ##
15
-
16
-
17
- class OciRepository(abc.ABC):
18
- @abc.abstractmethod
19
- def read_blob(self, digest: str) -> bytes:
20
- raise NotImplementedError
21
-
22
- @abc.abstractmethod
23
- def ref_blob(self, digest: str) -> OciDataRef:
24
- raise NotImplementedError
25
-
26
-
27
- #
28
-
29
-
30
- class DirectoryOciRepository(OciRepository):
31
- def __init__(self, data_dir: str) -> None:
32
- super().__init__()
33
-
34
- self._data_dir = check.non_empty_str(data_dir)
35
-
36
- def read_file(self, path: str) -> bytes:
37
- full_path = os.path.join(self._data_dir, path)
38
- check.arg(is_path_in_dir(self._data_dir, full_path))
39
- with open(full_path, 'rb') as f:
40
- return f.read()
41
-
42
- def blob_path(self, digest: str) -> str:
43
- scheme, value = digest.split(':')
44
- return os.path.join('blobs', scheme, value)
45
-
46
- def blob_full_path(self, digest: str) -> str:
47
- path = self.blob_path(digest)
48
- full_path = os.path.join(self._data_dir, path)
49
- check.arg(is_path_in_dir(self._data_dir, full_path))
50
- return full_path
51
-
52
- def read_blob(self, digest: str) -> bytes:
53
- return self.read_file(self.blob_path(digest))
54
-
55
- def ref_blob(self, digest: str) -> OciDataRef:
56
- return FileOciDataRef(self.blob_full_path(digest))
57
-
58
-
59
- #
60
-
61
-
62
- class DictionaryOciRepository(OciRepository):
63
- def __init__(self, blobs: ta.Mapping[str, bytes]) -> None:
64
- super().__init__()
65
-
66
- self._blobs = blobs
67
-
68
- def read_blob(self, digest: str) -> bytes:
69
- return self._blobs[digest]
70
-
71
- def ref_blob(self, digest: str) -> OciDataRef:
72
- return BytesOciDataRef(self._blobs[digest])
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes