ffmt 0.4.3__tar.gz → 0.4.4__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.
Files changed (63) hide show
  1. {ffmt-0.4.3 → ffmt-0.4.4}/Cargo.lock +26 -26
  2. {ffmt-0.4.3 → ffmt-0.4.4}/Cargo.toml +1 -1
  3. {ffmt-0.4.3 → ffmt-0.4.4}/PKG-INFO +1 -1
  4. {ffmt-0.4.3 → ffmt-0.4.4}/pyproject.toml +1 -1
  5. {ffmt-0.4.3 → ffmt-0.4.4}/src/classifier.rs +77 -1
  6. {ffmt-0.4.3 → ffmt-0.4.4}/tests/bugfixes.rs +60 -0
  7. {ffmt-0.4.3 → ffmt-0.4.4}/tests/classifier.rs +55 -0
  8. {ffmt-0.4.3 → ffmt-0.4.4}/.github/workflows/ci.yml +0 -0
  9. {ffmt-0.4.3 → ffmt-0.4.4}/.github/workflows/release.yml +0 -0
  10. {ffmt-0.4.3 → ffmt-0.4.4}/.github/workflows/update-readme-version.yml +0 -0
  11. {ffmt-0.4.3 → ffmt-0.4.4}/.gitignore +0 -0
  12. {ffmt-0.4.3 → ffmt-0.4.4}/.markdownlint.json +0 -0
  13. {ffmt-0.4.3 → ffmt-0.4.4}/.pre-commit-hooks.yaml +0 -0
  14. {ffmt-0.4.3 → ffmt-0.4.4}/.readthedocs.yml +0 -0
  15. {ffmt-0.4.3 → ffmt-0.4.4}/LICENSE +0 -0
  16. {ffmt-0.4.3 → ffmt-0.4.4}/README.md +0 -0
  17. {ffmt-0.4.3 → ffmt-0.4.4}/action.yml +0 -0
  18. {ffmt-0.4.3 → ffmt-0.4.4}/docs/ci.md +0 -0
  19. {ffmt-0.4.3 → ffmt-0.4.4}/docs/configuration.md +0 -0
  20. {ffmt-0.4.3 → ffmt-0.4.4}/docs/editors.md +0 -0
  21. {ffmt-0.4.3 → ffmt-0.4.4}/docs/getting-started.md +0 -0
  22. {ffmt-0.4.3 → ffmt-0.4.4}/docs/index.md +0 -0
  23. {ffmt-0.4.3 → ffmt-0.4.4}/docs/requirements.txt +0 -0
  24. {ffmt-0.4.3 → ffmt-0.4.4}/mkdocs.yml +0 -0
  25. {ffmt-0.4.3 → ffmt-0.4.4}/src/align.rs +0 -0
  26. {ffmt-0.4.3 → ffmt-0.4.4}/src/case_norm.rs +0 -0
  27. {ffmt-0.4.3 → ffmt-0.4.4}/src/cli.rs +0 -0
  28. {ffmt-0.4.3 → ffmt-0.4.4}/src/config.rs +0 -0
  29. {ffmt-0.4.3 → ffmt-0.4.4}/src/formatter.rs +0 -0
  30. {ffmt-0.4.3 → ffmt-0.4.4}/src/keyword_norm.rs +0 -0
  31. {ffmt-0.4.3 → ffmt-0.4.4}/src/lib.rs +0 -0
  32. {ffmt-0.4.3 → ffmt-0.4.4}/src/lsp.rs +0 -0
  33. {ffmt-0.4.3 → ffmt-0.4.4}/src/main.rs +0 -0
  34. {ffmt-0.4.3 → ffmt-0.4.4}/src/reader.rs +0 -0
  35. {ffmt-0.4.3 → ffmt-0.4.4}/src/scope.rs +0 -0
  36. {ffmt-0.4.3 → ffmt-0.4.4}/src/unicode.rs +0 -0
  37. {ffmt-0.4.3 → ffmt-0.4.4}/src/whitespace.rs +0 -0
  38. {ffmt-0.4.3 → ffmt-0.4.4}/tests/bughunt_classifier.rs +0 -0
  39. {ffmt-0.4.3 → ffmt-0.4.4}/tests/bughunt_cli.rs +0 -0
  40. {ffmt-0.4.3 → ffmt-0.4.4}/tests/bughunt_norm.rs +0 -0
  41. {ffmt-0.4.3 → ffmt-0.4.4}/tests/case_norm.rs +0 -0
  42. {ffmt-0.4.3 → ffmt-0.4.4}/tests/cli_bugs.rs +0 -0
  43. {ffmt-0.4.3 → ffmt-0.4.4}/tests/config_integration.rs +0 -0
  44. {ffmt-0.4.3 → ffmt-0.4.4}/tests/fixtures/blank_lines.expected.fpp +0 -0
  45. {ffmt-0.4.3 → ffmt-0.4.4}/tests/fixtures/blank_lines.input.fpp +0 -0
  46. {ffmt-0.4.3 → ffmt-0.4.4}/tests/fixtures/call_block.expected.fpp +0 -0
  47. {ffmt-0.4.3 → ffmt-0.4.4}/tests/fixtures/call_block.input.fpp +0 -0
  48. {ffmt-0.4.3 → ffmt-0.4.4}/tests/fixtures/directives.expected.fpp +0 -0
  49. {ffmt-0.4.3 → ffmt-0.4.4}/tests/fixtures/directives.input.fpp +0 -0
  50. {ffmt-0.4.3 → ffmt-0.4.4}/tests/fixtures/doxygen_spacing.expected.fpp +0 -0
  51. {ffmt-0.4.3 → ffmt-0.4.4}/tests/fixtures/doxygen_spacing.input.fpp +0 -0
  52. {ffmt-0.4.3 → ffmt-0.4.4}/tests/fixtures/fypp.expected.fpp +0 -0
  53. {ffmt-0.4.3 → ffmt-0.4.4}/tests/fixtures/fypp.input.fpp +0 -0
  54. {ffmt-0.4.3 → ffmt-0.4.4}/tests/fixtures/member_access_wrap.expected.fpp +0 -0
  55. {ffmt-0.4.3 → ffmt-0.4.4}/tests/fixtures/member_access_wrap.input.fpp +0 -0
  56. {ffmt-0.4.3 → ffmt-0.4.4}/tests/fixtures/simple.expected.fpp +0 -0
  57. {ffmt-0.4.3 → ffmt-0.4.4}/tests/fixtures/simple.input.fpp +0 -0
  58. {ffmt-0.4.3 → ffmt-0.4.4}/tests/fixtures/trailing_comment_wrap.expected.fpp +0 -0
  59. {ffmt-0.4.3 → ffmt-0.4.4}/tests/fixtures/trailing_comment_wrap.input.fpp +0 -0
  60. {ffmt-0.4.3 → ffmt-0.4.4}/tests/integration.rs +0 -0
  61. {ffmt-0.4.3 → ffmt-0.4.4}/tests/reader.rs +0 -0
  62. {ffmt-0.4.3 → ffmt-0.4.4}/tests/scope.rs +0 -0
  63. {ffmt-0.4.3 → ffmt-0.4.4}/tests/whitespace.rs +0 -0
@@ -63,12 +63,12 @@ dependencies = [
63
63
 
64
64
  [[package]]
65
65
  name = "bstr"
66
- version = "1.12.1"
66
+ version = "1.12.3"
67
67
  source = "registry+https://github.com/rust-lang/crates.io-index"
68
- checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
68
+ checksum = "5cee35f73844aa3014bb606320a6c1f010249dbdf43342fe54b5a4f6a8ed4b79"
69
69
  dependencies = [
70
70
  "memchr",
71
- "serde",
71
+ "serde_core",
72
72
  ]
73
73
 
74
74
  [[package]]
@@ -119,9 +119,9 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
119
119
 
120
120
  [[package]]
121
121
  name = "crossbeam-deque"
122
- version = "0.8.6"
122
+ version = "0.8.7"
123
123
  source = "registry+https://github.com/rust-lang/crates.io-index"
124
- checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
124
+ checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb"
125
125
  dependencies = [
126
126
  "crossbeam-epoch",
127
127
  "crossbeam-utils",
@@ -129,18 +129,18 @@ dependencies = [
129
129
 
130
130
  [[package]]
131
131
  name = "crossbeam-epoch"
132
- version = "0.9.18"
132
+ version = "0.9.20"
133
133
  source = "registry+https://github.com/rust-lang/crates.io-index"
134
- checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
134
+ checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
135
135
  dependencies = [
136
136
  "crossbeam-utils",
137
137
  ]
138
138
 
139
139
  [[package]]
140
140
  name = "crossbeam-utils"
141
- version = "0.8.21"
141
+ version = "0.8.22"
142
142
  source = "registry+https://github.com/rust-lang/crates.io-index"
143
- checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
143
+ checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
144
144
 
145
145
  [[package]]
146
146
  name = "either"
@@ -156,7 +156,7 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
156
156
 
157
157
  [[package]]
158
158
  name = "ffmt"
159
- version = "0.4.3"
159
+ version = "0.4.4"
160
160
  dependencies = [
161
161
  "clap",
162
162
  "ignore",
@@ -194,9 +194,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
194
194
 
195
195
  [[package]]
196
196
  name = "ignore"
197
- version = "0.4.26"
197
+ version = "0.4.28"
198
198
  source = "registry+https://github.com/rust-lang/crates.io-index"
199
- checksum = "b915661dd01db3f05050265b2477bcc6527b3792388e2749b41623cc592be67d"
199
+ checksum = "2adf14691c72bcfc1058740436a35bdd3ae9c07d1a941ef00b749e9ea16aefa7"
200
200
  dependencies = [
201
201
  "crossbeam-deque",
202
202
  "globset",
@@ -232,15 +232,15 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
232
232
 
233
233
  [[package]]
234
234
  name = "log"
235
- version = "0.4.32"
235
+ version = "0.4.33"
236
236
  source = "registry+https://github.com/rust-lang/crates.io-index"
237
- checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
237
+ checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
238
238
 
239
239
  [[package]]
240
240
  name = "memchr"
241
- version = "2.8.2"
241
+ version = "2.8.3"
242
242
  source = "registry+https://github.com/rust-lang/crates.io-index"
243
- checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
243
+ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
244
244
 
245
245
  [[package]]
246
246
  name = "once_cell_polyfill"
@@ -259,9 +259,9 @@ dependencies = [
259
259
 
260
260
  [[package]]
261
261
  name = "quote"
262
- version = "1.0.45"
262
+ version = "1.0.46"
263
263
  source = "registry+https://github.com/rust-lang/crates.io-index"
264
- checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
264
+ checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
265
265
  dependencies = [
266
266
  "proc-macro2",
267
267
  ]
@@ -288,9 +288,9 @@ dependencies = [
288
288
 
289
289
  [[package]]
290
290
  name = "regex"
291
- version = "1.12.4"
291
+ version = "1.13.0"
292
292
  source = "registry+https://github.com/rust-lang/crates.io-index"
293
- checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
293
+ checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2"
294
294
  dependencies = [
295
295
  "aho-corasick",
296
296
  "memchr",
@@ -300,9 +300,9 @@ dependencies = [
300
300
 
301
301
  [[package]]
302
302
  name = "regex-automata"
303
- version = "0.4.14"
303
+ version = "0.4.15"
304
304
  source = "registry+https://github.com/rust-lang/crates.io-index"
305
- checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
305
+ checksum = "1f388202e4b80542a0921078cc23b6333bcf1409c1e3f86404cae4766a6131db"
306
306
  dependencies = [
307
307
  "aho-corasick",
308
308
  "memchr",
@@ -384,9 +384,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
384
384
 
385
385
  [[package]]
386
386
  name = "syn"
387
- version = "2.0.117"
387
+ version = "2.0.118"
388
388
  source = "registry+https://github.com/rust-lang/crates.io-index"
389
- checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
389
+ checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
390
390
  dependencies = [
391
391
  "proc-macro2",
392
392
  "quote",
@@ -491,6 +491,6 @@ dependencies = [
491
491
 
492
492
  [[package]]
493
493
  name = "zmij"
494
- version = "1.0.21"
494
+ version = "1.0.23"
495
495
  source = "registry+https://github.com/rust-lang/crates.io-index"
496
- checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
496
+ checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "ffmt"
3
- version = "0.4.3"
3
+ version = "0.4.4"
4
4
  edition = "2021"
5
5
  description = "A fast, configurable Fortran formatter with Fypp, Doxygen, and OpenACC/OpenMP support"
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ffmt
3
- Version: 0.4.3
3
+ Version: 0.4.4
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: Intended Audience :: Science/Research
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "ffmt"
7
- version = "0.4.3"
7
+ version = "0.4.4"
8
8
  description = "A fast, configurable Fortran formatter with Fypp, Doxygen, and OpenACC/OpenMP support"
9
9
  license = { text = "MIT" }
10
10
  requires-python = ">=3.8"
@@ -480,6 +480,66 @@ fn classify_fortran(trimmed: &str) -> LineKind {
480
480
  LineKind::FortranStatement
481
481
  }
482
482
 
483
+ /// Split a Fortran line into its top-level statements at semicolons, ignoring
484
+ /// semicolons inside string literals and stopping at an inline comment.
485
+ /// Returns the trimmed pieces (empty pieces are kept so callers can skip them).
486
+ fn split_top_level_statements(line: &str) -> Vec<&str> {
487
+ let bytes = line.as_bytes();
488
+ let mut pieces = Vec::new();
489
+ let mut start = 0;
490
+ let mut in_string = false;
491
+ let mut quote = b' ';
492
+ let mut i = 0;
493
+ while i < bytes.len() {
494
+ let b = bytes[i];
495
+ if in_string {
496
+ if b == quote {
497
+ // Doubled quote is an escape — stay inside the string.
498
+ if i + 1 < bytes.len() && bytes[i + 1] == quote {
499
+ i += 2;
500
+ continue;
501
+ }
502
+ in_string = false;
503
+ }
504
+ i += 1;
505
+ continue;
506
+ }
507
+ match b {
508
+ b'\'' | b'"' => {
509
+ in_string = true;
510
+ quote = b;
511
+ }
512
+ b'!' => break, // inline comment — nothing after it is a statement
513
+ b';' => {
514
+ pieces.push(line[start..i].trim());
515
+ start = i + 1;
516
+ }
517
+ _ => {}
518
+ }
519
+ i += 1;
520
+ }
521
+ pieces.push(line[start..].trim());
522
+ pieces
523
+ }
524
+
525
+ /// Net indentation effect of a line's top-level statements: +1 per block
526
+ /// opener, -1 per block closer, 0 for everything else (continuations,
527
+ /// statements). A self-contained single-line construct nets to 0.
528
+ fn net_block_delta(pieces: &[&str]) -> i32 {
529
+ let mut delta = 0;
530
+ for &piece in pieces {
531
+ if piece.is_empty() {
532
+ continue;
533
+ }
534
+ match classify_fortran(piece) {
535
+ LineKind::FortranBlockOpen => delta += 1,
536
+ LineKind::FortranBlockClose => delta -= 1,
537
+ _ => {}
538
+ }
539
+ }
540
+ delta
541
+ }
542
+
483
543
  /// Classify a logical line.
484
544
  pub fn classify(line: &str) -> LineKind {
485
545
  let trimmed = line.trim();
@@ -518,7 +578,23 @@ pub fn classify(line: &str) -> LineKind {
518
578
  }
519
579
 
520
580
  // 6. Fortran
521
- classify_fortran(trimmed)
581
+ let kind = classify_fortran(trimmed);
582
+
583
+ // A construct that opens AND closes on the same line (e.g.
584
+ // `do d = 1, n; s = s + d; end do`) has a net-zero indent effect. The
585
+ // leading keyword makes `classify_fortran` report a block opener, which
586
+ // would leak an indent level onto every following line. When the opener's
587
+ // matching closer is on the same line (net delta 0), treat the whole line
588
+ // as a plain statement instead — a block opener and a statement both sit at
589
+ // the current depth, so the line's own indentation is unchanged.
590
+ if kind == LineKind::FortranBlockOpen {
591
+ let pieces = split_top_level_statements(trimmed);
592
+ if pieces.len() > 1 && net_block_delta(&pieces) == 0 {
593
+ return LineKind::FortranStatement;
594
+ }
595
+ }
596
+
597
+ kind
522
598
  }
523
599
 
524
600
  /// Extract the scope name from a Fortran block-opening line.
@@ -726,3 +726,63 @@ fn test_keyword_case_upper_works() {
726
726
  "identifier was uppercased:\n{out}"
727
727
  );
728
728
  }
729
+
730
+ // --- Single-line `do ...; end do` leaked an indent level (issue #5) ---
731
+
732
+ #[test]
733
+ fn test_single_line_do_does_not_leak_indent() {
734
+ // The `do ...; ...; end do` opens and closes on one line (net-zero indent).
735
+ // The following statement must stay at the SAME indent as the `do` line,
736
+ // not be over-indented by one level.
737
+ let src = "module m\n\
738
+ contains\n\
739
+ subroutine demo(n)\n\
740
+ integer, intent(in) :: n\n\
741
+ integer :: d, s\n\
742
+ s = 0\n\
743
+ do d = 1, n; s = s + d; end do\n\
744
+ s = s + 1\n\
745
+ end subroutine demo\n\
746
+ end module m\n";
747
+ let out = ffmt::format_string(src);
748
+ assert!(
749
+ out.contains(" do d = 1, n; s = s + d; end do\n s = s + 1\n"),
750
+ "single-line do leaked an indent level:\n{out}"
751
+ );
752
+ }
753
+
754
+ #[test]
755
+ fn test_single_line_do_output_is_idempotent() {
756
+ // Running ffmt twice must be a no-op (the correct output is the fixed point).
757
+ let src = "module m\n\
758
+ contains\n\
759
+ subroutine demo(n)\n\
760
+ integer, intent(in) :: n\n\
761
+ integer :: d, s\n\
762
+ s = 0\n\
763
+ do d = 1, n; s = s + d; end do\n\
764
+ s = s + 1\n\
765
+ end subroutine demo\n\
766
+ end module m\n";
767
+ let once = ffmt::format_string(src);
768
+ let twice = ffmt::format_string(&once);
769
+ assert_eq!(once, twice, "formatting is not idempotent");
770
+ }
771
+
772
+ #[test]
773
+ fn test_nested_single_line_do_does_not_compound_indent() {
774
+ // Two single-line constructs in sequence must each net to zero; the final
775
+ // statement stays at the subroutine-body indent (8 spaces).
776
+ let src = "program p\n\
777
+ integer :: i, j, s\n\
778
+ s = 0\n\
779
+ do i = 1, 3; s = s + i; end do\n\
780
+ if (s > 0) then; s = s + 1; end if\n\
781
+ s = s + 1\n\
782
+ end program p\n";
783
+ let out = ffmt::format_string(src);
784
+ assert!(
785
+ out.contains(" s = s + 1\nend program p\n"),
786
+ "single-line constructs compounded the indent:\n{out}"
787
+ );
788
+ }
@@ -387,3 +387,58 @@ fn test_interface_operator() {
387
387
  LineKind::FortranBlockOpen
388
388
  );
389
389
  }
390
+
391
+ // --- Self-contained single-line constructs (issue #5) ---
392
+ // A construct that opens and closes on the same line has a net-zero indent
393
+ // effect, so it must classify as a statement (not a block opener), otherwise
394
+ // the trailing lines leak an indent level.
395
+ #[test]
396
+ fn test_single_line_do_is_statement() {
397
+ assert_eq!(
398
+ classify("do d = 1, n; s = s + d; end do"),
399
+ LineKind::FortranStatement
400
+ );
401
+ }
402
+ #[test]
403
+ fn test_single_line_if_then_is_statement() {
404
+ assert_eq!(
405
+ classify("if (x > 0) then; y = 1; end if"),
406
+ LineKind::FortranStatement
407
+ );
408
+ }
409
+ #[test]
410
+ fn test_single_line_block_is_statement() {
411
+ assert_eq!(
412
+ classify("block; call foo; end block"),
413
+ LineKind::FortranStatement
414
+ );
415
+ }
416
+ #[test]
417
+ fn test_single_line_associate_is_statement() {
418
+ assert_eq!(
419
+ classify("associate (a => b); x = a; end associate"),
420
+ LineKind::FortranStatement
421
+ );
422
+ }
423
+ #[test]
424
+ fn test_single_line_if_then_else_is_statement() {
425
+ // A continuation (else) between the opener and closer must not disturb the
426
+ // net-zero balance.
427
+ assert_eq!(
428
+ classify("if (x) then; a = 1; else; a = 2; end if"),
429
+ LineKind::FortranStatement
430
+ );
431
+ }
432
+ #[test]
433
+ fn test_do_with_body_no_end_stays_open() {
434
+ // Opener with a same-line body but NO closer still opens a scope.
435
+ assert_eq!(classify("do i = 1, n; a = 1"), LineKind::FortranBlockOpen);
436
+ }
437
+ #[test]
438
+ fn test_do_reopening_after_close_stays_open() {
439
+ // Opens, closes, opens again: net +1 → still a block opener.
440
+ assert_eq!(
441
+ classify("do i = 1, n; end do; do j = 1, m"),
442
+ LineKind::FortranBlockOpen
443
+ );
444
+ }
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
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