launchpy 0.4.3.post3__tar.gz → 0.4.4.post1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {launchpy-0.4.3.post3/launchpy.egg-info → launchpy-0.4.4.post1}/PKG-INFO +5 -2
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1}/docs/library.md +8 -7
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1}/docs/property.md +8 -0
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1}/docs/releases.md +10 -0
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1}/docs/synchronizer.md +9 -1
- launchpy-0.4.4.post1/launchpy/__version__.py +1 -0
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1}/launchpy/library.py +22 -2
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1}/launchpy/property.py +58 -8
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1}/launchpy/synchronizer.py +21 -1
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1/launchpy.egg-info}/PKG-INFO +5 -2
- launchpy-0.4.3.post3/launchpy/__version__.py +0 -1
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1}/LICENSE +0 -0
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1}/README.md +0 -0
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1}/docs/admin.md +0 -0
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1}/docs/getstarted.md +0 -0
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1}/docs/main.md +0 -0
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1}/docs/translator.md +0 -0
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1}/launchpy/__init__.py +0 -0
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1}/launchpy/admin.py +0 -0
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1}/launchpy/config.py +0 -0
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1}/launchpy/configs.py +0 -0
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1}/launchpy/connector.py +0 -0
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1}/launchpy/launchpy.py +0 -0
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1}/launchpy.egg-info/SOURCES.txt +0 -0
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1}/launchpy.egg-info/dependency_links.txt +0 -0
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1}/launchpy.egg-info/requires.txt +0 -0
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1}/launchpy.egg-info/top_level.txt +0 -0
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1}/pyproject.toml +0 -0
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1}/requirements.txt +0 -0
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1}/setup.cfg +0 -0
- {launchpy-0.4.3.post3 → launchpy-0.4.4.post1}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: launchpy
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.4.post1
|
|
4
4
|
Summary: Adobe Launch API wrapper
|
|
5
5
|
Home-page: https://github.com/pitchmuc/launchpy
|
|
6
6
|
Author: Julien Piccini
|
|
@@ -30,6 +30,9 @@ Requires-Dist: pathlib2
|
|
|
30
30
|
Requires-Dist: pathlib
|
|
31
31
|
Provides-Extra: dynamic
|
|
32
32
|
Requires-Dist: version; extra == "dynamic"
|
|
33
|
+
Dynamic: author
|
|
34
|
+
Dynamic: home-page
|
|
35
|
+
Dynamic: requires-python
|
|
33
36
|
|
|
34
37
|
# Launchpy
|
|
35
38
|
|
|
@@ -33,10 +33,10 @@ The different attributes are the following:
|
|
|
33
33
|
|
|
34
34
|
* id : id of the library
|
|
35
35
|
* name : name of the library
|
|
36
|
-
* state : is it
|
|
36
|
+
* state : is it "development", "staging" or "production" environment
|
|
37
37
|
* build_required : is a build required or not (Boolean)
|
|
38
38
|
* builds : The build attache to this library
|
|
39
|
-
* buid_status : Is the last build has been
|
|
39
|
+
* buid_status : Is the last build has been "successful" ?
|
|
40
40
|
* relationships : the different element attached to this Library
|
|
41
41
|
* _environments = a dictionary of the different environment possible.
|
|
42
42
|
|
|
@@ -45,10 +45,11 @@ The different attributes are the following:
|
|
|
45
45
|
The get methods will enable you to retrieve the elements that have been attached to this library.
|
|
46
46
|
The different get methods are the followings:
|
|
47
47
|
|
|
48
|
-
* getDataElements
|
|
49
|
-
* getExtensions
|
|
50
|
-
* getRules
|
|
48
|
+
* getDataElements : get the data element in that library
|
|
49
|
+
* getExtensions : get the Extension in that library
|
|
50
|
+
* getRules : get the rules in the library
|
|
51
51
|
* getFullLibrary : It is a combinaison of all of the 3 above.
|
|
52
|
+
* getBuilds : Retrieve the builds of that library
|
|
52
53
|
|
|
53
54
|
## Adding element to a library
|
|
54
55
|
|
|
@@ -62,7 +63,7 @@ They are the following:
|
|
|
62
63
|
|
|
63
64
|
## Environments settings
|
|
64
65
|
|
|
65
|
-
Before you can actually start building the library and pass it from one state to another (
|
|
66
|
+
Before you can actually start building the library and pass it from one state to another ("development" -> "staging"). You would need to set the different environments.
|
|
66
67
|
|
|
67
68
|
The method used for that is : setEnvironments().
|
|
68
69
|
It takes 2 arguments:
|
|
@@ -90,7 +91,7 @@ myLib.build() # require that you have set your environment.
|
|
|
90
91
|
As it is your goal to publish your library, you want to transition it from the dev to the staging environment and so forth.
|
|
91
92
|
In order to do that, you can use the transition() method.
|
|
92
93
|
***Note*** : You would need to build your library between 2 transition.
|
|
93
|
-
At the end of the funnel, you just need to build your library when you are in the
|
|
94
|
+
At the end of the funnel, you just need to build your library when you are in the "approved" state.
|
|
94
95
|
The transition method takes 1 argument:
|
|
95
96
|
|
|
96
97
|
* action : it can be either
|
|
@@ -98,6 +98,10 @@ The different get methods are the following :
|
|
|
98
98
|
Arguments:
|
|
99
99
|
* element : REQUIRED : the element definition dictionary
|
|
100
100
|
|
|
101
|
+
* **getProductionEndpoint** : Get the URL for the production library
|
|
102
|
+
|
|
103
|
+
* **getStagingEndpoint** : Get the URL for the staging library
|
|
104
|
+
|
|
101
105
|
|
|
102
106
|
## Create methods
|
|
103
107
|
|
|
@@ -322,6 +326,10 @@ They are not propertly defined by the Launch API, they have some business logic
|
|
|
322
326
|
* save : OPTIONAL : bool, if you want to save the setting in a JS or JSON file, set it to true. (default False)
|
|
323
327
|
* encoding : OPTIONAL : encoding to be used for saving the file.
|
|
324
328
|
|
|
329
|
+
* **extractRuleComponents** : Extract the ruleComponents reference URL from the rule definition. It can be useful for extracting rule components from the last published rule.
|
|
330
|
+
Arguments:
|
|
331
|
+
* rule : REQUIRED : A dictionary that provide the definition of the rule.
|
|
332
|
+
|
|
325
333
|
* **findRuleComponentSettingsFileName** : Return the filename use to save your custom code of your ruleComponent in a file using the extractSettings method.\
|
|
326
334
|
Returns None when this is not a Custom code from CORE or Adobe Analytics. \
|
|
327
335
|
Argument:
|
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
This page gathered the changes made between version of the launchpy module.\
|
|
4
4
|
This has been started after the 0.3.0 release.\
|
|
5
5
|
|
|
6
|
+
## 0.4.4
|
|
7
|
+
* adding a `getBuilds` method on the `Library` class instance
|
|
8
|
+
* extend support for pagination on `getEnvironments`
|
|
9
|
+
* adding `getProductionEndpoint` and `getStagingEndpoint` to retrieve the URL to use on websites
|
|
10
|
+
* support the instantiation of the `Library` class via a single libraryId string.\
|
|
11
|
+
Patch:
|
|
12
|
+
* adding a name parameter for the `checkExtensionUpdate` method
|
|
13
|
+
* adding the `extractRuleComponents` method in property.
|
|
14
|
+
|
|
15
|
+
|
|
6
16
|
## 0.4.3
|
|
7
17
|
* Refactor `Translator` class for removing `pandas` dependency\
|
|
8
18
|
Patch:
|
|
@@ -240,4 +240,12 @@ The logic that will be applied during the sync is the following:
|
|
|
240
240
|
* IF the component name is in that list, **and not** in the `exclComponents` list, it will be sync
|
|
241
241
|
* IF the component name is in that list **and** in the `exclComponents` list, it will **NOT** be sync
|
|
242
242
|
* IF there is no list of `inclComponents` for that target property (or empty list), **and not** in the `exclComponents` list, the component will be synced
|
|
243
|
-
* ELSE: it will be sync
|
|
243
|
+
* ELSE: it will be sync
|
|
244
|
+
|
|
245
|
+
### Regular expression in Python
|
|
246
|
+
|
|
247
|
+
One common issue seeing the pattern used in Regular Expression is the use of the quantifier `*` without any character before.\
|
|
248
|
+
This can lead to some errors when using the following regular expression: `*some pattern*`\
|
|
249
|
+
This type of pattern needs to be replaced by `.*some pattern.*`\
|
|
250
|
+
|
|
251
|
+
The `*` is a quantifier that needs to have a character before it so it can know which character or pattern to repeat (or not).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.4-1"
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import time
|
|
2
2
|
# Non standard libraries
|
|
3
3
|
from launchpy import config, connector
|
|
4
|
+
from typing import Union
|
|
4
5
|
|
|
5
6
|
class Library:
|
|
6
7
|
"""
|
|
7
8
|
A class that handle the library in a Launch environment.
|
|
8
9
|
"""
|
|
9
10
|
|
|
10
|
-
def __init__(self, data: dict,config_object:dict=config.config_object,header:dict=config.header):
|
|
11
|
+
def __init__(self, data: Union[str,dict],config_object:dict=config.config_object,header:dict=config.header):
|
|
11
12
|
"""
|
|
12
13
|
The instantiator for the library.
|
|
13
14
|
Arguments:
|
|
@@ -19,6 +20,8 @@ class Library:
|
|
|
19
20
|
config_object=config_object, header=header)
|
|
20
21
|
self.header = self.connector.header
|
|
21
22
|
self.endpoint = config.endpoints['global']
|
|
23
|
+
if type(data) == str:
|
|
24
|
+
data = self.connector.getData("https://reactor.adobe.io/libraries/"+data).get('data')
|
|
22
25
|
self.id = data['id']
|
|
23
26
|
self.name = data['attributes']['name']
|
|
24
27
|
self.state = data['attributes']['state']
|
|
@@ -100,6 +103,23 @@ class Library:
|
|
|
100
103
|
return dataOrigin
|
|
101
104
|
self.relationships['extensions'] = data
|
|
102
105
|
return data
|
|
106
|
+
|
|
107
|
+
def getBuilds(self)->list:
|
|
108
|
+
"""
|
|
109
|
+
Retrieve the last builds.
|
|
110
|
+
Return a list of build
|
|
111
|
+
"""
|
|
112
|
+
params = {'page[number]':1}
|
|
113
|
+
res = self.connector.getData(self._Builds,params=params)
|
|
114
|
+
builds = res.get('data',[])
|
|
115
|
+
next_page = res.get('meta',{}).get('pagination',{}).get('next_page',None)
|
|
116
|
+
while next_page is not None:
|
|
117
|
+
params['page[number]'] +=1
|
|
118
|
+
res = self.connector.getData(self._Builds,params=params)
|
|
119
|
+
builds += res.get('data',[])
|
|
120
|
+
next_page = res.get('meta',{}).get('pagination',{}).get('next_page',None)
|
|
121
|
+
return builds
|
|
122
|
+
|
|
103
123
|
|
|
104
124
|
def getRules(self,page:int=0,pageSize:int=50,origin:bool=True)->list:
|
|
105
125
|
"""
|
|
@@ -296,7 +316,7 @@ class Library:
|
|
|
296
316
|
Save the different environments ids available.
|
|
297
317
|
It is required to use the library class.
|
|
298
318
|
Arguments :
|
|
299
|
-
environments_list : REQUIRED : list of environment retrieved by the
|
|
319
|
+
environments_list : REQUIRED : list of environment retrieved by the getEnvironments method
|
|
300
320
|
dev_name : OPTIONAL : Name of your dev environment. If not defined, will take the first dev environment.
|
|
301
321
|
"""
|
|
302
322
|
for env in environments_list:
|
|
@@ -99,8 +99,15 @@ class Property:
|
|
|
99
99
|
"""
|
|
100
100
|
Retrieve the environment sets for this property
|
|
101
101
|
"""
|
|
102
|
+
params = {"page[number]":1}
|
|
102
103
|
env = self.connector.getData(self._Environments)
|
|
103
104
|
data = env['data'] # skip meta for now
|
|
105
|
+
next_page = env.get('meta',{}).get('pagination',{}).get('next_page',None)
|
|
106
|
+
while next_page is not None:
|
|
107
|
+
params['page[number]'] = next_page
|
|
108
|
+
env = self.connector.getData(self._Environments,params=params)
|
|
109
|
+
data += env.get('data',[])
|
|
110
|
+
next_page = env.get('meta',{}).get('pagination',{}).get('next_page',None)
|
|
104
111
|
return data
|
|
105
112
|
|
|
106
113
|
def getHost(self)->object:
|
|
@@ -139,13 +146,14 @@ class Property:
|
|
|
139
146
|
data = extensions
|
|
140
147
|
return data
|
|
141
148
|
|
|
142
|
-
def checkExtensionUpdate(self, platform: str = "web", verbose: bool = False):
|
|
149
|
+
def checkExtensionUpdate(self, name:str=None, platform: str = "web", verbose: bool = False):
|
|
143
150
|
"""
|
|
144
151
|
Returns a dictionary of extensions with their names, ids and if there is an update.
|
|
145
152
|
If there is an update available, the id returned is the latest id (to be used for installation).
|
|
146
153
|
It can be re-use for installation and for checking for update.
|
|
147
154
|
Arguments:
|
|
148
|
-
|
|
155
|
+
name : OPTIONAL : If you want to check for a specific extension
|
|
156
|
+
platform : OPTIONAL : if you need to look for extension on a specific platform (default web).
|
|
149
157
|
verbose: OPTIONAL : if set to True, will print the different name and id of the extensions checked.
|
|
150
158
|
Dictionary example:
|
|
151
159
|
{'adobe-mcid':
|
|
@@ -155,13 +163,18 @@ class Property:
|
|
|
155
163
|
}
|
|
156
164
|
"""
|
|
157
165
|
extensions = self.getExtensions()
|
|
158
|
-
|
|
166
|
+
if name is None:
|
|
167
|
+
dict_extensions = {ext['attributes']['name']: {'package_id': ext['relationships']['extension_package']['data']['id'],
|
|
159
168
|
'update': False,
|
|
160
169
|
'internal_id': ext['id']
|
|
161
170
|
} for ext in extensions}
|
|
171
|
+
else:
|
|
172
|
+
dict_extensions = {ext['attributes']['name']: {'package_id': ext['relationships']['extension_package']['data']['id'],
|
|
173
|
+
'update': False,
|
|
174
|
+
'internal_id': ext['id']
|
|
175
|
+
} for ext in extensions if ext['attributes']['name'] == name}
|
|
162
176
|
for name in dict_extensions:
|
|
163
|
-
new_id = self._getExtensionPackage(
|
|
164
|
-
name, platform=platform, verbose=verbose)
|
|
177
|
+
new_id = self._getExtensionPackage(name, platform=platform, verbose=verbose)
|
|
165
178
|
if new_id != dict_extensions[name]['package_id']:
|
|
166
179
|
dict_extensions[name]['package_id'] = new_id
|
|
167
180
|
dict_extensions[name]['update'] = True
|
|
@@ -258,9 +271,9 @@ class Property:
|
|
|
258
271
|
"""
|
|
259
272
|
filters = {}
|
|
260
273
|
if name != None:
|
|
261
|
-
filters['filter[name]
|
|
274
|
+
filters['filter[name]'] = f"EQ {name}"
|
|
262
275
|
if name_contains != None:
|
|
263
|
-
filters['filter[name]
|
|
276
|
+
filters['filter[name]'] = f"CONTAINS {name_contains}"
|
|
264
277
|
if dirty != None:
|
|
265
278
|
filters['filter[dirty]'] = f"EQ {str(dirty).lower()}"
|
|
266
279
|
if enabled != None:
|
|
@@ -297,6 +310,27 @@ class Property:
|
|
|
297
310
|
'url': rule['links']['rule_components']
|
|
298
311
|
}
|
|
299
312
|
return data
|
|
313
|
+
|
|
314
|
+
def extractRuleComponents(self,rule:dict=None)->list:
|
|
315
|
+
"""
|
|
316
|
+
Extract the rule component based on the rule definition.
|
|
317
|
+
Arguments:
|
|
318
|
+
rule : REQUIRED : The rule definition
|
|
319
|
+
"""
|
|
320
|
+
if rule is None:
|
|
321
|
+
raise ValueError("No rule definition provided")
|
|
322
|
+
params={'page[number]':0}
|
|
323
|
+
rc_endpoint = rule['relationships']['rule_components']['links']['related']
|
|
324
|
+
page1 = self.getRessource(rc_endpoint)
|
|
325
|
+
data = page1.get('data',{})
|
|
326
|
+
next_page = page1.get('meta',{}).get('pagination',{}).get('next_page',None)
|
|
327
|
+
while next_page is not None:
|
|
328
|
+
params['page[number]'] = next_page
|
|
329
|
+
pageN = self.connector.getData(rc_endpoint,params=params)
|
|
330
|
+
data += pageN.get('data',[])
|
|
331
|
+
next_page = pageN.get('meta',{}).get('pagination',{}).get('next_page',None)
|
|
332
|
+
return data
|
|
333
|
+
|
|
300
334
|
|
|
301
335
|
def getRuleComponents(self, verbose:bool=False,**kwargs)->dict:
|
|
302
336
|
"""
|
|
@@ -1187,6 +1221,22 @@ class Property:
|
|
|
1187
1221
|
for de in dataelements:
|
|
1188
1222
|
self.deleteDataElement(de['id'])
|
|
1189
1223
|
return res
|
|
1224
|
+
|
|
1225
|
+
def getProductionEndpoint(self)->dict:
|
|
1226
|
+
"""
|
|
1227
|
+
Returns the production library URL to use on the website
|
|
1228
|
+
"""
|
|
1229
|
+
envs = self.getEnvironments()
|
|
1230
|
+
prod = [env for env in envs if env['attributes']['stage'] == 'production'][0]
|
|
1231
|
+
return prod['meta']['script_sources'][0]["license_path"]
|
|
1232
|
+
|
|
1233
|
+
def getStagingEndpoint(self)->dict:
|
|
1234
|
+
"""
|
|
1235
|
+
Returns the staging library URL to use on the website
|
|
1236
|
+
"""
|
|
1237
|
+
envs = self.getEnvironments()
|
|
1238
|
+
staging = [env for env in envs if env['attributes']['stage'] == 'staging'][0]
|
|
1239
|
+
return staging['meta']['script_sources'][0]["license_path"]
|
|
1190
1240
|
|
|
1191
1241
|
def extensionsInfo(data: list)->dict:
|
|
1192
1242
|
"""
|
|
@@ -1551,4 +1601,4 @@ def extractAnalyticsCode(rcSettings: str, save: bool = False, filename: str = No
|
|
|
1551
1601
|
filename = filename.replace('/', '_').replace('|', '_')
|
|
1552
1602
|
if save:
|
|
1553
1603
|
saveFile(json_code,filename,type='js',encoding=encoding)
|
|
1554
|
-
return json_code
|
|
1604
|
+
return json_code
|
|
@@ -37,7 +37,7 @@ class Synchronizer:
|
|
|
37
37
|
raise KeyError("The base property name has not been found in your account")
|
|
38
38
|
self.base = {}
|
|
39
39
|
self.base["name"] = base
|
|
40
|
-
self.base["api"] = Property(base_property[0])
|
|
40
|
+
self.base["api"]:Property = Property(base_property[0])
|
|
41
41
|
self.base['rules'] = self.base['api'].getRules()
|
|
42
42
|
self.base['dataElements'] = self.base['api'].getDataElements()
|
|
43
43
|
self.base['extensions'] = self.base['api'].getExtensions()
|
|
@@ -353,6 +353,26 @@ class Synchronizer:
|
|
|
353
353
|
if len(newDataElements)>0:
|
|
354
354
|
self.targets[target]['library'].addDataElements(newDataElements)
|
|
355
355
|
|
|
356
|
+
def upgradeTargetExtension(self,extensionName:str=None,platform:str="web")->dict:
|
|
357
|
+
"""
|
|
358
|
+
Upgrade the name extension in the target properties.
|
|
359
|
+
Arguments:
|
|
360
|
+
extensionName : REQUIRED : The name of the extension to upgrade.
|
|
361
|
+
ex : "core" or "adobe-analytics"
|
|
362
|
+
platform : OPTIONAL : If you want to update the extension of a specific platform (default "web")
|
|
363
|
+
"""
|
|
364
|
+
if extensionName is None:
|
|
365
|
+
raise ValueError("Require an extension name")
|
|
366
|
+
for prop, target in self.targets:
|
|
367
|
+
try:
|
|
368
|
+
extensionUpdate = target['api'].checkExtensionUpdate(extensionName)
|
|
369
|
+
except:
|
|
370
|
+
raise ValueError(f"Could not find an extension name: {extensionName}")
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
|
|
356
376
|
def renameComponent(self,old_name:str=None,new_name:str=None)->None:
|
|
357
377
|
"""
|
|
358
378
|
Passing the old and new name of a component, it will rename the component in the different target properties.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: launchpy
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.4.post1
|
|
4
4
|
Summary: Adobe Launch API wrapper
|
|
5
5
|
Home-page: https://github.com/pitchmuc/launchpy
|
|
6
6
|
Author: Julien Piccini
|
|
@@ -30,6 +30,9 @@ Requires-Dist: pathlib2
|
|
|
30
30
|
Requires-Dist: pathlib
|
|
31
31
|
Provides-Extra: dynamic
|
|
32
32
|
Requires-Dist: version; extra == "dynamic"
|
|
33
|
+
Dynamic: author
|
|
34
|
+
Dynamic: home-page
|
|
35
|
+
Dynamic: requires-python
|
|
33
36
|
|
|
34
37
|
# Launchpy
|
|
35
38
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.4.3-3"
|
|
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
|
|
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
|