slthcore 0.1.4__tar.gz → 0.1.5__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.

Potentially problematic release.


This version of slthcore might be problematic. Click here for more details.

Files changed (83) hide show
  1. {slthcore-0.1.4/slthcore.egg-info → slthcore-0.1.5}/PKG-INFO +1 -1
  2. {slthcore-0.1.4 → slthcore-0.1.5}/setup.py +1 -1
  3. slthcore-0.1.5/slth/pdf/__init__.py +177 -0
  4. slthcore-0.1.5/slth/pdf/tests.py +70 -0
  5. {slthcore-0.1.4 → slthcore-0.1.5}/slth/selenium/__init__.py +1 -0
  6. {slthcore-0.1.4 → slthcore-0.1.5/slthcore.egg-info}/PKG-INFO +1 -1
  7. {slthcore-0.1.4 → slthcore-0.1.5}/slthcore.egg-info/SOURCES.txt +2 -0
  8. {slthcore-0.1.4 → slthcore-0.1.5}/MANIFEST.in +0 -0
  9. {slthcore-0.1.4 → slthcore-0.1.5}/setup.cfg +0 -0
  10. {slthcore-0.1.4 → slthcore-0.1.5}/slth/__init__.py +0 -0
  11. {slthcore-0.1.4 → slthcore-0.1.5}/slth/cmd/configure/__main__.py +0 -0
  12. {slthcore-0.1.4 → slthcore-0.1.5}/slth/cmd/init/__main__.py +0 -0
  13. {slthcore-0.1.4 → slthcore-0.1.5}/slth/cmd/init/boilerplate/.DS_Store +0 -0
  14. {slthcore-0.1.4 → slthcore-0.1.5}/slth/cmd/init/boilerplate/.gitignore +0 -0
  15. {slthcore-0.1.4 → slthcore-0.1.5}/slth/cmd/init/boilerplate/backend/api/__init__.py +0 -0
  16. {slthcore-0.1.4 → slthcore-0.1.5}/slth/cmd/init/boilerplate/backend/api/asgi.py +0 -0
  17. {slthcore-0.1.4 → slthcore-0.1.5}/slth/cmd/init/boilerplate/backend/api/endpoints.py +0 -0
  18. {slthcore-0.1.4 → slthcore-0.1.5}/slth/cmd/init/boilerplate/backend/api/models.py +0 -0
  19. {slthcore-0.1.4 → slthcore-0.1.5}/slth/cmd/init/boilerplate/backend/api/settings.py +0 -0
  20. {slthcore-0.1.4 → slthcore-0.1.5}/slth/cmd/init/boilerplate/backend/api/tests.py +0 -0
  21. {slthcore-0.1.4 → slthcore-0.1.5}/slth/cmd/init/boilerplate/backend/api/urls.py +0 -0
  22. {slthcore-0.1.4 → slthcore-0.1.5}/slth/cmd/init/boilerplate/backend/api/wsgi.py +0 -0
  23. {slthcore-0.1.4 → slthcore-0.1.5}/slth/cmd/init/boilerplate/backend/application.yml +0 -0
  24. {slthcore-0.1.4 → slthcore-0.1.5}/slth/cmd/init/boilerplate/backend/entrypoint.sh +0 -0
  25. {slthcore-0.1.4 → slthcore-0.1.5}/slth/cmd/init/boilerplate/backend/manage.py +0 -0
  26. {slthcore-0.1.4 → slthcore-0.1.5}/slth/cmd/init/boilerplate/backend/requirements.txt +0 -0
  27. {slthcore-0.1.4 → slthcore-0.1.5}/slth/cmd/init/boilerplate/base.env +0 -0
  28. {slthcore-0.1.4 → slthcore-0.1.5}/slth/cmd/init/boilerplate/docker-compose.yml +0 -0
  29. {slthcore-0.1.4 → slthcore-0.1.5}/slth/cmd/init/boilerplate/frontend/package.json +0 -0
  30. {slthcore-0.1.4 → slthcore-0.1.5}/slth/cmd/init/boilerplate/frontend/src/main.jsx +0 -0
  31. {slthcore-0.1.4 → slthcore-0.1.5}/slth/cmd/init/boilerplate/frontend/vite.config.js +0 -0
  32. {slthcore-0.1.4 → slthcore-0.1.5}/slth/cmd/init/boilerplate/local.env +0 -0
  33. {slthcore-0.1.4 → slthcore-0.1.5}/slth/cmd/init/boilerplate/run.sh +0 -0
  34. {slthcore-0.1.4 → slthcore-0.1.5}/slth/cmd/init/boilerplate/selenium/run.sh +0 -0
  35. {slthcore-0.1.4 → slthcore-0.1.5}/slth/cmd/init/boilerplate/test.sh +0 -0
  36. {slthcore-0.1.4 → slthcore-0.1.5}/slth/components.py +0 -0
  37. {slthcore-0.1.4 → slthcore-0.1.5}/slth/db/__init__.py +0 -0
  38. {slthcore-0.1.4 → slthcore-0.1.5}/slth/db/generic.py +0 -0
  39. {slthcore-0.1.4 → slthcore-0.1.5}/slth/db/models.py +0 -0
  40. {slthcore-0.1.4 → slthcore-0.1.5}/slth/endpoints.py +0 -0
  41. {slthcore-0.1.4 → slthcore-0.1.5}/slth/exceptions.py +0 -0
  42. {slthcore-0.1.4 → slthcore-0.1.5}/slth/factory.py +0 -0
  43. {slthcore-0.1.4 → slthcore-0.1.5}/slth/forms.py +0 -0
  44. {slthcore-0.1.4 → slthcore-0.1.5}/slth/management/__init__.py +0 -0
  45. {slthcore-0.1.4 → slthcore-0.1.5}/slth/management/commands/__init__.py +0 -0
  46. {slthcore-0.1.4 → slthcore-0.1.5}/slth/management/commands/integration_test.py +0 -0
  47. {slthcore-0.1.4 → slthcore-0.1.5}/slth/management/commands/sync.py +0 -0
  48. {slthcore-0.1.4 → slthcore-0.1.5}/slth/migrations/0001_initial.py +0 -0
  49. {slthcore-0.1.4 → slthcore-0.1.5}/slth/migrations/0002_email_role_pushsubscription_error.py +0 -0
  50. {slthcore-0.1.4 → slthcore-0.1.5}/slth/migrations/0003_rename_photo_profile_alter_profile_options.py +0 -0
  51. {slthcore-0.1.4 → slthcore-0.1.5}/slth/migrations/0004_alter_profile_photo.py +0 -0
  52. {slthcore-0.1.4 → slthcore-0.1.5}/slth/migrations/0005_alter_profile_photo.py +0 -0
  53. {slthcore-0.1.4 → slthcore-0.1.5}/slth/migrations/0006_user.py +0 -0
  54. {slthcore-0.1.4 → slthcore-0.1.5}/slth/migrations/0007_deletion_log.py +0 -0
  55. {slthcore-0.1.4 → slthcore-0.1.5}/slth/migrations/__init__.py +0 -0
  56. {slthcore-0.1.4 → slthcore-0.1.5}/slth/models.py +0 -0
  57. {slthcore-0.1.4 → slthcore-0.1.5}/slth/notifications.py +0 -0
  58. {slthcore-0.1.4 → slthcore-0.1.5}/slth/oauth.py +0 -0
  59. {slthcore-0.1.4 → slthcore-0.1.5}/slth/permissions.py +0 -0
  60. {slthcore-0.1.4 → slthcore-0.1.5}/slth/printer.py +0 -0
  61. {slthcore-0.1.4 → slthcore-0.1.5}/slth/queryset.py +0 -0
  62. {slthcore-0.1.4 → slthcore-0.1.5}/slth/roles.py +0 -0
  63. {slthcore-0.1.4 → slthcore-0.1.5}/slth/selenium/browser.py +0 -0
  64. {slthcore-0.1.4 → slthcore-0.1.5}/slth/serializer.py +0 -0
  65. {slthcore-0.1.4 → slthcore-0.1.5}/slth/static/.DS_Store +0 -0
  66. {slthcore-0.1.4 → slthcore-0.1.5}/slth/static/css/.DS_Store +0 -0
  67. {slthcore-0.1.4 → slthcore-0.1.5}/slth/static/css/slth.css +0 -0
  68. {slthcore-0.1.4 → slthcore-0.1.5}/slth/static/js/index.min.js +0 -0
  69. {slthcore-0.1.4 → slthcore-0.1.5}/slth/static/js/react.min.js +0 -0
  70. {slthcore-0.1.4 → slthcore-0.1.5}/slth/static/js/slth.min.js +0 -0
  71. {slthcore-0.1.4 → slthcore-0.1.5}/slth/statistics.py +0 -0
  72. {slthcore-0.1.4 → slthcore-0.1.5}/slth/tasks.py +0 -0
  73. {slthcore-0.1.4 → slthcore-0.1.5}/slth/templates/index.html +0 -0
  74. {slthcore-0.1.4 → slthcore-0.1.5}/slth/templates/report.html +0 -0
  75. {slthcore-0.1.4 → slthcore-0.1.5}/slth/templates/service-worker.js +0 -0
  76. {slthcore-0.1.4 → slthcore-0.1.5}/slth/templates/signature.html +0 -0
  77. {slthcore-0.1.4 → slthcore-0.1.5}/slth/tests.py +0 -0
  78. {slthcore-0.1.4 → slthcore-0.1.5}/slth/threadlocal.py +0 -0
  79. {slthcore-0.1.4 → slthcore-0.1.5}/slth/urls.py +0 -0
  80. {slthcore-0.1.4 → slthcore-0.1.5}/slth/utils.py +0 -0
  81. {slthcore-0.1.4 → slthcore-0.1.5}/slth/views.py +0 -0
  82. {slthcore-0.1.4 → slthcore-0.1.5}/slthcore.egg-info/dependency_links.txt +0 -0
  83. {slthcore-0.1.4 → slthcore-0.1.5}/slthcore.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: slthcore
3
- Version: 0.1.4
3
+ Version: 0.1.5
4
4
  Summary: API generator based on yml file
5
5
  Home-page: https://github.com/brenokcc
6
6
  Author: Breno Silva
@@ -5,7 +5,7 @@ install_requires = []
5
5
 
6
6
  setup(
7
7
  name='slthcore',
8
- version='0.1.4',
8
+ version='0.1.5',
9
9
  packages=find_packages(),
10
10
  install_requires=install_requires,
11
11
  include_package_data=True,
@@ -0,0 +1,177 @@
1
+ # pip install --upgrade fpdf2
2
+ from fpdf import FPDF, FontFace
3
+ import hashlib
4
+ import base64
5
+ import os
6
+ import re
7
+ import requests
8
+ from datetime import datetime
9
+ import tempfile
10
+ from PIL import Image, ImageDraw, ImageFont
11
+ from django.template.loader import render_to_string
12
+ from django.conf import settings
13
+
14
+ SIGNATURE_URL = 'https://assinatura-api.staging.iti.br/externo/v2/assinarPKCS7'
15
+ SIGNATURE_SIZE = 18944
16
+ BYTERANGE='[ ]'
17
+
18
+ STYLE = {
19
+ 'h1': FontFace(color="#000000", size_pt=28, family='Helvetica'),
20
+ 'h2': FontFace(color="#000000", size_pt=24, family='Helvetica'),
21
+ }
22
+
23
+ class PdfWriter:
24
+ def __init__(self):
25
+ self.pdf = FPDF()
26
+ self.pdf.add_page()
27
+
28
+ def render(self, template_name, context):
29
+ context.update(base_url=settings.SITE_URL)
30
+ html = render_to_string(template_name, context)
31
+ self.pdf.write_html(html, tag_styles=STYLE, font_family="Courier")
32
+
33
+ def save(self, path):
34
+ self.pdf.output(path)
35
+
36
+
37
+ class PdfSigner:
38
+ def __init__(self, path, signer):
39
+ self.path = path
40
+ self.signer = signer
41
+ self.xref = None
42
+ self.data = []
43
+ with open(path, 'rb') as fp:
44
+ self.content = fp.read()# + b'\r\n'
45
+ self.offset = len(self.content)
46
+ self.byterange = []
47
+ self.path = path.split('.')[0]
48
+ self.startxref = [int(x[9:].strip()) for x in re.findall(b'startxref[\n\r ]*\d+', self.content)]
49
+ self.objects = {}
50
+ for xref in re.findall(b'xref[\n\r\w\d ]*trailer', self.content[self.startxref[0]:]):
51
+ for offset in sum([[int(n) for n in re.findall(b'\d{10}', xref) if int(n)]], []):
52
+ self.objects[b' '.join(self.content[offset:offset + 15].split()[0:2]).decode()] = offset
53
+ self.trailer = self.content[self.startxref[-1]:].split(b'trailer')[1].split(b'startxref')[0].strip()
54
+ self.root = root = re.findall(b'/Root \d+ \d+ R', self.trailer)[-1][6:-2].decode()
55
+ self.size = int(re.findall(b'/Size \d+', self.trailer)[-1][5:].strip())
56
+ self.page()
57
+
58
+ def obj(self, key=None):
59
+ key = key or self.root
60
+ return self.content[self.objects[key]: self.objects[key] + self.content[self.objects[key]:].index(b'endobj') + 6].replace(b'\r', b'') + b'\n'
61
+
62
+ def next(self, i):
63
+ return max([int(k.split()[0]) for k in self.objects]) + i
64
+
65
+ def add(self, data):
66
+ offset = str(self.offset).rjust(10, '0')
67
+ self.offset += len(data)
68
+ self.data.append(data)
69
+ if self.xref is None:
70
+ self.xref = ['xref', '0 1', '0000000000 65535 f\r']
71
+ self.xref.append('{} 1'.format(data.split()[0]))
72
+ self.xref.append('{} 00000 n\r'.format(offset))
73
+
74
+ def page(self):
75
+ for key, offset in self.objects.items():
76
+ content = self.content[offset:offset + 500]
77
+ if b'/Page' in content and b'/Pages' not in content:
78
+ return key
79
+ raise BaseException('No page was found in the PDF')
80
+
81
+ def finalize(self):
82
+ xrefstart = str(self.offset)
83
+ data = '\n'.join(self.xref)
84
+ data += '\ntrailer\n<<\n/Size {}\n/Root {} R\n/Prev {}\n>>\n'.format(
85
+ (self.size + len(self.xref) - 5), self.root, self.startxref[-1]
86
+ )
87
+ data += 'startxref\n'
88
+ data += xrefstart
89
+ data += '\n%%EOF'
90
+ self.offset += len(data)
91
+ self.data.append(data)
92
+
93
+ def hash(self, n=0):
94
+ w, h = 900, 200
95
+ left = n * 200
96
+ signer = self.signer.split(':')[0]
97
+ stamp_file_path = self.stamp(signer)
98
+ print(stamp_file_path)
99
+ self.add(self.obj().decode().replace('>>', '/AcroForm <<\n/Fields [{} 0 R]\n/SigFlags 3\n>>\n>>'.format(self.next(1))))
100
+ self.add(self.obj(self.page()).decode().replace('/Page', '/Page /Annots [{} 0 R]'.format(self.next(1))))
101
+ a = "{} 0 obj\n<<\n/FT /Sig\n/Type /Annot\n/Subtype /Widget\n/F 132\n/T (Signature1)\n/V {} 0 R\n/P {} R\n/Rect [{} 0 {} {}]\n/AP {} 0 R\n>>\nendobj\n".format(
102
+ self.next(1), self.next(2), self.page(), w/10*2+left, left, h/10*2, self.next(3))
103
+ b = "{} 0 obj\n<<\n/Type /Sig\n/Filter /Adobe.PPKLite\n/SubFilter /adbe.pkcs7.detached\n/M ({})\n/Contents <{}>\n/ByteRange {}\n/Name ({})\n>>\nendobj\n".format(
104
+ self.next(2), datetime.now().strftime("D:%Y%m%d%H%M%S+00'00'"), '0' * SIGNATURE_SIZE, BYTERANGE, signer)
105
+ c = "{} 0 obj\n<<\n/N {} 0 R\n>>\nendobj\n".format(self.next(3), self.next(4))
106
+ stream = 'q\n{} 0 0 {} 0 0 cm\n/Img1 Do\nQ'.format(w, h)
107
+ d = "{} 0 obj\n<</Type/XObject/Subtype/Form/Resources<</XObject<</Img1 {} 0 R>>>>/BBox[0 0 {} {}]/Length {}>>\nstream\n{}\nendstream\nendobj\n".format(self.next(4), self.next(5), w, h, len(stream), stream)
108
+ x = base64.a85encode(open(stamp_file_path, 'rb').read()).decode()
109
+ e = "{} 0 obj\n<<\n/BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter [ /ASCII85Decode /DCTDecode ] /Length {} /Subtype /Image /Type /XObject /Width {} /Height {} /Resources<< >> >>\nstream\n{}\nendstream\nendobj\n".format(self.next(5), len(x), w, h, x)
110
+ self.add(a)
111
+ i = self.offset + b.index('/Contents') + 10
112
+ j = i + SIGNATURE_SIZE + 2
113
+ self.add(b)
114
+ self.add(c)
115
+ self.add(d)
116
+ self.add(e)
117
+ self.finalize()
118
+ self.byterange = [0, i, j, self.length() - j]
119
+ self.data[3] = self.data[3].replace(
120
+ BYTERANGE, '[{} {} {} {}]'.format(*self.byterange).ljust(len(BYTERANGE), ' ')
121
+ )
122
+ for data in self.data:
123
+ self.content += data.encode()
124
+ i, j, x, y = self.byterange
125
+ content = self.content[i:i+j] + self.content[x:x+y]
126
+ hash = base64.b64encode(hashlib.sha256(content).digest()).decode()
127
+ return hash
128
+
129
+ def length(self):
130
+ i = len(self.content)
131
+ for data in self.data:
132
+ i += len(data)
133
+ return i
134
+
135
+ def signed(self, name):
136
+ return '/Name ({})'.format(name).encode() in self.content
137
+
138
+ def stamp(self, name):
139
+ w, h = 900, 200
140
+ bgcolor = '#FFFFFF'
141
+ date = datetime.now()
142
+ img = Image.new('RGB', (w, h), (255, 255, 255))
143
+ img1 = ImageDraw.Draw(img)
144
+ img1.rectangle([(3, 3), (w - 3, h - 3)], fill=bgcolor, outline=bgcolor)
145
+ font = ImageFont.truetype(os.path.join('/Users/breno/Documents/Workspace/suap', 'comum', 'static/comum/font/MicrosoftSansSerif.ttf'), 25)
146
+ bold = ImageFont.truetype(os.path.join('/Users/breno/Documents/Workspace/suap', 'comum', 'static/comum/font/MicrosoftSansSerifBold.ttf'), 25)
147
+ x, y = 380, 10
148
+ img1.text((x, y), 'DOCUMENTO ASSINADO DIGITALMENTE', (0, 0, 0), font=font)
149
+ y += 45
150
+ img1.text((x, y), name, (0, 0, 0), font=bold)
151
+ y += 35
152
+ img1.text((x, y), 'Data/Hora: {}'.format(date.strftime('%d/%m/%Y %H:%M:%S')), (0, 0, 0), font=font)
153
+ y += 35
154
+ img1.text((x, y), 'Verifique em https://verificador.iti.br', (0, 0, 0), font=font)
155
+ y += 35
156
+ tmp = tempfile.NamedTemporaryFile(suffix='.jpeg', delete=False)
157
+ logo = Image.open(os.path.join(os.path.dirname(__file__), 'images', 'icp-brasil.png'))
158
+ img.paste(logo.resize((int(logo.width*0.6), int(logo.height*0.6))), (5, 10))
159
+ img = img.convert("RGB")
160
+ img.save(tmp.name, format='jpeg')
161
+ return tmp.name
162
+
163
+ def sign(self, path=None):
164
+ signature = self.sign_hash(self.hash())
165
+ path = path or '{}-signed.pdf'.format(self.path)
166
+ with open(path, 'wb') as fp:
167
+ content = self.content
168
+ if signature:
169
+ i, j, x, y = self.byterange
170
+ content = self.content[i:i + j] + b'<' + base64.b64decode(signature).hex().ljust(SIGNATURE_SIZE, '0').encode() + b'>' + self.content[x:x + y]
171
+ fp.write(content)
172
+
173
+ def sign_hash(self, hash):
174
+ return None
175
+
176
+
177
+ #curl -X POST 'https://verificador.staging.iti.br/report' --header 'Content-Type: multipart/form-data' --form 'report_type="json"' --form 'signature_files[]=@"hello-assinado.pdf"' --form 'detached_files[]=""'
@@ -0,0 +1,70 @@
1
+ from django.test import TestCase
2
+ from . import PdfWriter, VidaasPdfSigner
3
+
4
+ HTML = """
5
+ <h1>Big title</h1>
6
+ <dl>
7
+ <dt>Description title</dt>
8
+ <dd>Description Detail</dd>
9
+ </dl>
10
+ <section>
11
+ <h2>Section title</h2>
12
+ <p><b>Hello</b> world. <u>I am</u> <i>tired</i>.</p>
13
+ <p><a href="https://github.com/py-pdf/fpdf2">py-pdf/fpdf2 GitHub repo</a></p>
14
+ <p align="right">right aligned text</p>
15
+ <p>i am a paragraph <br>in two parts.</p>
16
+ <font color="#00ff00"><p>hello in green</p></font>
17
+ <font size="7"><p>hello small</p></font>
18
+ <font face="helvetica"><p>hello helvetica</p></font>
19
+ <font face="times"><p>hello times</p></font>
20
+ </section>
21
+ <section>
22
+ <h2>Other section title</h2>
23
+ <ul type="circle">
24
+ <li>unordered</li>
25
+ <li>list</li>
26
+ <li>items</li>
27
+ </ul>
28
+ <ol start="3" type="i">
29
+ <li>ordered</li>
30
+ <li>list</li>
31
+ <li>items</li>
32
+ </ol>
33
+ <br>
34
+ <br>
35
+ <pre>i am preformatted text.</pre>
36
+ <br>
37
+ <blockquote>hello blockquote</blockquote>
38
+ <table width="50%">
39
+ <thead>
40
+ <tr>
41
+ <th width="30%">ID</th>
42
+ <th width="70%">Name</th>
43
+ </tr>
44
+ </thead>
45
+ <tbody>
46
+ <tr>
47
+ <td>1</td>
48
+ <td>Alice</td>
49
+ </tr>
50
+ <tr>
51
+ <td>2</td>
52
+ <td>Bob</td>
53
+ </tr>
54
+ </tbody>
55
+ </table>
56
+ </section>
57
+ """
58
+
59
+ class AnimalTestCase(TestCase):
60
+ def setUp(self):
61
+ return super().setUp()
62
+
63
+ def test_pdf(self):
64
+ writter = PdfWriter(HTML)
65
+ writter.save('test4.pdf')
66
+ signer = VidaasPdfSigner('test4.pdf', 'Carlos Breno Pereira Silva:04770402414')
67
+ signer.sign()
68
+
69
+ def tearDown(self):
70
+ return super().tearDown()
@@ -78,6 +78,7 @@ class SeleniumTestCase(LiveServerTestCase):
78
78
  if os.path.exists(fixture_path):
79
79
  call_command("loaddata", fixture_path)
80
80
  settings.DEBUG = True
81
+ settings.SITE_URL = cls.live_server_url
81
82
 
82
83
  def create_superuser(self, username, password):
83
84
  if not User.objects.filter(username=username).exists():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: slthcore
3
- Version: 0.1.4
3
+ Version: 0.1.5
4
4
  Summary: API generator based on yml file
5
5
  Home-page: https://github.com/brenokcc
6
6
  Author: Breno Silva
@@ -61,6 +61,8 @@ slth/migrations/0005_alter_profile_photo.py
61
61
  slth/migrations/0006_user.py
62
62
  slth/migrations/0007_deletion_log.py
63
63
  slth/migrations/__init__.py
64
+ slth/pdf/__init__.py
65
+ slth/pdf/tests.py
64
66
  slth/selenium/__init__.py
65
67
  slth/selenium/browser.py
66
68
  slth/static/.DS_Store
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes