awsquery 0.3.1__tar.gz → 0.3.2__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.
- {awsquery-0.3.1 → awsquery-0.3.2}/PKG-INFO +1 -1
- {awsquery-0.3.1 → awsquery-0.3.2}/pyproject.toml +1 -1
- {awsquery-0.3.1 → awsquery-0.3.2}/src/awsquery/cli.py +41 -3
- {awsquery-0.3.1 → awsquery-0.3.2}/src/awsquery.egg-info/PKG-INFO +1 -1
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/unit/test_cli_arg_processing.py +170 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/.claude/agents/code-reviewer.md +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/.claude/agents/makefile-optimizer.md +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/.claude/agents/python-infra-automator.md +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/.claude/agents/test-writer.md +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/.claude/settings.local.json +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/.coveragerc +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/.devcontainer/devcontainer.json +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/.dockerignore +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/.flake8 +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/.github/workflows/quality.yml +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/.github/workflows/release.yml +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/.github/workflows/test.yml +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/.gitignore +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/CLAUDE.md +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/Dockerfile +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/Dockerfile.prod +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/LICENSE +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/Makefile +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/README.md +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/Todo.md +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/docker-compose.yml +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/docs/DOCKER.md +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/docs/mutation-testing.md +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/policy.json +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/pytest.ini +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/requirements.txt +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/setup.cfg +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/src/awsquery/__init__.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/src/awsquery/config.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/src/awsquery/core.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/src/awsquery/default_filters.yaml +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/src/awsquery/filters.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/src/awsquery/formatters.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/src/awsquery/policy.json +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/src/awsquery/security.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/src/awsquery/utils.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/src/awsquery.egg-info/SOURCES.txt +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/src/awsquery.egg-info/dependency_links.txt +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/src/awsquery.egg-info/entry_points.txt +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/src/awsquery.egg-info/requires.txt +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/src/awsquery.egg-info/top_level.txt +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/__init__.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/conftest.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/fixtures/__init__.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/fixtures/aws_responses.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/fixtures/policy_samples.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/integration/__init__.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/integration/test_autocomplete_resilience.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/integration/test_end_to_end.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/integration/test_feature_combinations.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/integration/test_keys_behavior.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/integration/test_multi_level.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/integration/test_multi_level_calls.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/integration/test_session_management.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/unit/__init__.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/unit/test_cli_flags.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/unit/test_cli_parser.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/unit/test_cli_session_args.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/unit/test_column_ordering.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/unit/test_config_loading_simple.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/unit/test_core.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/unit/test_core_inference.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/unit/test_core_string_handling.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/unit/test_critical_mutations.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/unit/test_default_column_filters.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/unit/test_filter_edge_cases.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/unit/test_filter_implementation.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/unit/test_filter_matching.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/unit/test_filter_parameter_extraction.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/unit/test_filters.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/unit/test_filters_pattern_matching.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/unit/test_formatter_edge_cases.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/unit/test_formatters.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/unit/test_formatters_data_processing.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/unit/test_keys_display_fix.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/unit/test_messaging_enhancements.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/unit/test_security.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/unit/test_security_edge_cases.py +0 -0
- {awsquery-0.3.1 → awsquery-0.3.2}/tests/unit/test_tags_transformation.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "awsquery"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.2"
|
|
8
8
|
description = "Advanced CLI tool for querying AWS APIs with flexible filtering and automatic parameter resolution"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "Florian Motlik", email = "flo@flomotlik.me" }]
|
|
@@ -7,6 +7,7 @@ import sys
|
|
|
7
7
|
|
|
8
8
|
import argcomplete
|
|
9
9
|
import boto3
|
|
10
|
+
from argcomplete import warn
|
|
10
11
|
|
|
11
12
|
from .config import apply_default_filters
|
|
12
13
|
from .core import (
|
|
@@ -201,6 +202,30 @@ def determine_column_filters(column_filters, service, action):
|
|
|
201
202
|
return None
|
|
202
203
|
|
|
203
204
|
|
|
205
|
+
def _enhanced_completion_validator(completion_candidate, current_input):
|
|
206
|
+
"""Custom argcomplete validator for enhanced action matching."""
|
|
207
|
+
if not current_input:
|
|
208
|
+
return True
|
|
209
|
+
|
|
210
|
+
current_input_lower = current_input.lower()
|
|
211
|
+
candidate_lower = completion_candidate.lower()
|
|
212
|
+
|
|
213
|
+
# 1. Exact prefix match (highest priority)
|
|
214
|
+
if candidate_lower.startswith(current_input_lower):
|
|
215
|
+
return True
|
|
216
|
+
|
|
217
|
+
# 2. Partial substring match
|
|
218
|
+
if current_input_lower in candidate_lower:
|
|
219
|
+
return True
|
|
220
|
+
|
|
221
|
+
# 3. Split match - all parts must be found as substrings
|
|
222
|
+
parts = [part for part in current_input_lower.split("-") if part] # Filter empty parts
|
|
223
|
+
if len(parts) > 1 and all(part in candidate_lower for part in parts):
|
|
224
|
+
return True
|
|
225
|
+
|
|
226
|
+
return False
|
|
227
|
+
|
|
228
|
+
|
|
204
229
|
def action_completer(prefix, parsed_args, **kwargs):
|
|
205
230
|
"""Autocomplete action names based on selected service"""
|
|
206
231
|
if not parsed_args.service:
|
|
@@ -240,8 +265,21 @@ def action_completer(prefix, parsed_args, **kwargs):
|
|
|
240
265
|
kebab_case = re.sub("([a-z0-9])([A-Z])", r"\1-\2", op).lower()
|
|
241
266
|
cli_operations.append(kebab_case)
|
|
242
267
|
|
|
243
|
-
|
|
244
|
-
|
|
268
|
+
# Return all valid operations - let argcomplete validator handle filtering
|
|
269
|
+
all_operations = sorted(cli_operations)
|
|
270
|
+
|
|
271
|
+
if prefix:
|
|
272
|
+
# Check what will actually match with our validator
|
|
273
|
+
matched = [op for op in all_operations if _enhanced_completion_validator(op, prefix)]
|
|
274
|
+
|
|
275
|
+
# If multiple matches share a common prefix shorter than input, warn user
|
|
276
|
+
if len(matched) > 1:
|
|
277
|
+
common = os.path.commonprefix(matched)
|
|
278
|
+
if common and len(common) <= len(prefix):
|
|
279
|
+
# Show user what matches were found
|
|
280
|
+
warn(f"Matches for '{prefix}': {', '.join(matched)}")
|
|
281
|
+
|
|
282
|
+
return all_operations
|
|
245
283
|
except Exception:
|
|
246
284
|
# If we can't get operations, return empty
|
|
247
285
|
return []
|
|
@@ -299,7 +337,7 @@ Examples:
|
|
|
299
337
|
) # pragma: no mutate
|
|
300
338
|
action_arg.completer = action_completer # type: ignore[attr-defined]
|
|
301
339
|
|
|
302
|
-
argcomplete.autocomplete(parser)
|
|
340
|
+
argcomplete.autocomplete(parser, validator=_enhanced_completion_validator)
|
|
303
341
|
|
|
304
342
|
# First pass: parse known args to get service and action
|
|
305
343
|
args, remaining = parser.parse_known_args()
|
|
@@ -7,6 +7,7 @@ from unittest.mock import MagicMock, patch
|
|
|
7
7
|
import pytest
|
|
8
8
|
|
|
9
9
|
from awsquery.cli import (
|
|
10
|
+
_enhanced_completion_validator,
|
|
10
11
|
_extract_flag_and_value,
|
|
11
12
|
_extract_flags_from_args,
|
|
12
13
|
_preserve_parsed_flags,
|
|
@@ -409,3 +410,172 @@ class TestActionCompleter:
|
|
|
409
410
|
|
|
410
411
|
# Should include describe action but not create (fallback behavior)
|
|
411
412
|
assert "describe-instances" in result
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
class TestActionCompleterEnhanced:
|
|
416
|
+
"""Test enhanced completion validator with partial and split matching."""
|
|
417
|
+
|
|
418
|
+
def test_partial_string_match_middle(self):
|
|
419
|
+
"""Test partial string matching in the middle of action name."""
|
|
420
|
+
result = _enhanced_completion_validator("get-caller-identity", "caller")
|
|
421
|
+
assert result is True
|
|
422
|
+
|
|
423
|
+
result = _enhanced_completion_validator("get-session-token", "caller")
|
|
424
|
+
assert result is False
|
|
425
|
+
|
|
426
|
+
def test_partial_string_match_end(self):
|
|
427
|
+
"""Test partial string matching at the end of action name."""
|
|
428
|
+
result = _enhanced_completion_validator("get-caller-identity", "identity")
|
|
429
|
+
assert result is True
|
|
430
|
+
|
|
431
|
+
result = _enhanced_completion_validator("get-session-token", "identity")
|
|
432
|
+
assert result is False
|
|
433
|
+
|
|
434
|
+
def test_split_match_basic(self):
|
|
435
|
+
"""Test split matching with basic example call-ide -> get-caller-identity."""
|
|
436
|
+
result = _enhanced_completion_validator("get-caller-identity", "call-ide")
|
|
437
|
+
assert result is True
|
|
438
|
+
|
|
439
|
+
result = _enhanced_completion_validator("get-session-token", "call-ide")
|
|
440
|
+
assert result is False
|
|
441
|
+
|
|
442
|
+
def test_split_match_multiple_parts(self):
|
|
443
|
+
"""Test split matching with ses-tok -> get-session-token."""
|
|
444
|
+
result = _enhanced_completion_validator("get-session-token", "ses-tok")
|
|
445
|
+
assert result is True
|
|
446
|
+
|
|
447
|
+
result = _enhanced_completion_validator("get-caller-identity", "ses-tok")
|
|
448
|
+
assert result is False
|
|
449
|
+
|
|
450
|
+
def test_split_match_partial_segments(self):
|
|
451
|
+
"""Test split matching with partial segments cal-iden -> get-caller-identity."""
|
|
452
|
+
result = _enhanced_completion_validator("get-caller-identity", "cal-iden")
|
|
453
|
+
assert result is True
|
|
454
|
+
|
|
455
|
+
result = _enhanced_completion_validator("get-session-token", "cal-iden")
|
|
456
|
+
assert result is False
|
|
457
|
+
|
|
458
|
+
def test_prefix_matching_priority(self):
|
|
459
|
+
"""Test that exact prefix match works."""
|
|
460
|
+
result = _enhanced_completion_validator("get-caller-identity", "get")
|
|
461
|
+
assert result is True
|
|
462
|
+
|
|
463
|
+
result = _enhanced_completion_validator("get-session-token", "get")
|
|
464
|
+
assert result is True
|
|
465
|
+
|
|
466
|
+
result = _enhanced_completion_validator("assume-role", "get")
|
|
467
|
+
assert result is False
|
|
468
|
+
|
|
469
|
+
def test_case_insensitive_matching(self):
|
|
470
|
+
"""Test that matching is case insensitive."""
|
|
471
|
+
result = _enhanced_completion_validator("get-caller-identity", "caller")
|
|
472
|
+
assert result is True
|
|
473
|
+
|
|
474
|
+
result = _enhanced_completion_validator("get-caller-identity", "CALLER")
|
|
475
|
+
assert result is True
|
|
476
|
+
|
|
477
|
+
result = _enhanced_completion_validator("get-caller-identity", "CaLLer")
|
|
478
|
+
assert result is True
|
|
479
|
+
|
|
480
|
+
result = _enhanced_completion_validator("GET-CALLER-IDENTITY", "caller")
|
|
481
|
+
assert result is True
|
|
482
|
+
|
|
483
|
+
def test_empty_input_returns_true(self):
|
|
484
|
+
"""Test that empty input returns True (shows all options)."""
|
|
485
|
+
result = _enhanced_completion_validator("get-caller-identity", "")
|
|
486
|
+
assert result is True
|
|
487
|
+
|
|
488
|
+
result = _enhanced_completion_validator("any-action", "")
|
|
489
|
+
assert result is True
|
|
490
|
+
|
|
491
|
+
def test_no_matches_returns_false(self):
|
|
492
|
+
"""Test that non-matching input returns False."""
|
|
493
|
+
result = _enhanced_completion_validator("get-caller-identity", "xyz123")
|
|
494
|
+
assert result is False
|
|
495
|
+
|
|
496
|
+
result = _enhanced_completion_validator("get-session-token", "nonexistent")
|
|
497
|
+
assert result is False
|
|
498
|
+
|
|
499
|
+
def test_split_match_with_empty_parts(self):
|
|
500
|
+
"""Test split matching handles empty parts correctly (e.g., 'call--ide')."""
|
|
501
|
+
result = _enhanced_completion_validator("get-caller-identity", "call--ide")
|
|
502
|
+
assert result is True
|
|
503
|
+
|
|
504
|
+
result = _enhanced_completion_validator("get-session-token", "call--ide")
|
|
505
|
+
assert result is False
|
|
506
|
+
|
|
507
|
+
def test_split_match_requires_multiple_parts(self):
|
|
508
|
+
"""Test that split matching only applies when input has multiple parts."""
|
|
509
|
+
# Single part should use substring matching only
|
|
510
|
+
result = _enhanced_completion_validator("get-caller-identity", "caller")
|
|
511
|
+
assert result is True
|
|
512
|
+
|
|
513
|
+
# Multi-part should use split matching
|
|
514
|
+
result = _enhanced_completion_validator("get-caller-identity", "get-caller")
|
|
515
|
+
assert result is True
|
|
516
|
+
|
|
517
|
+
result = _enhanced_completion_validator("get-caller-identity", "caller-identity")
|
|
518
|
+
assert result is True
|
|
519
|
+
|
|
520
|
+
def test_action_completer_returns_all_operations(self):
|
|
521
|
+
"""Test that action_completer now returns all operations without filtering."""
|
|
522
|
+
with patch("awsquery.cli.validate_readonly") as mock_validate, patch(
|
|
523
|
+
"botocore.session.Session"
|
|
524
|
+
) as mock_session_class, patch.dict("os.environ", {}, clear=True):
|
|
525
|
+
mock_session = MagicMock()
|
|
526
|
+
mock_session.get_available_services.return_value = ["sts"]
|
|
527
|
+
mock_service_model = MagicMock()
|
|
528
|
+
mock_service_model.operation_names = ["GetCallerIdentity", "GetSessionToken"]
|
|
529
|
+
mock_session.get_service_model.return_value = mock_service_model
|
|
530
|
+
mock_session_class.return_value = mock_session
|
|
531
|
+
mock_validate.return_value = True
|
|
532
|
+
|
|
533
|
+
parsed_args = argparse.Namespace(service="sts")
|
|
534
|
+
|
|
535
|
+
# Should return all operations regardless of prefix
|
|
536
|
+
result_all = action_completer("", parsed_args)
|
|
537
|
+
result_specific = action_completer("caller", parsed_args)
|
|
538
|
+
result_nonmatch = action_completer("xyz123", parsed_args)
|
|
539
|
+
|
|
540
|
+
# All should return the same complete list
|
|
541
|
+
expected = ["get-caller-identity", "get-session-token"]
|
|
542
|
+
assert result_all == expected
|
|
543
|
+
assert result_specific == expected
|
|
544
|
+
assert result_nonmatch == expected
|
|
545
|
+
|
|
546
|
+
@patch("awsquery.cli.warn")
|
|
547
|
+
@patch("awsquery.cli.validate_readonly")
|
|
548
|
+
@patch("botocore.session.Session")
|
|
549
|
+
@patch.dict("os.environ", {}, clear=True)
|
|
550
|
+
def test_action_completer_warns_on_common_prefix_with_split_matching(
|
|
551
|
+
self, mock_session_class, mock_validate, mock_warn
|
|
552
|
+
):
|
|
553
|
+
"""Test that action_completer warns with split matching and common prefix."""
|
|
554
|
+
mock_session = MagicMock()
|
|
555
|
+
mock_session.get_available_services.return_value = ["elasticache"]
|
|
556
|
+
mock_service_model = MagicMock()
|
|
557
|
+
mock_service_model.operation_names = [
|
|
558
|
+
"DescribeReplicationGroups",
|
|
559
|
+
"DescribeGlobalReplicationGroups",
|
|
560
|
+
]
|
|
561
|
+
mock_session.get_service_model.return_value = mock_service_model
|
|
562
|
+
mock_session_class.return_value = mock_session
|
|
563
|
+
mock_validate.return_value = True
|
|
564
|
+
|
|
565
|
+
parsed_args = argparse.Namespace(service="elasticache")
|
|
566
|
+
|
|
567
|
+
# This should trigger a warning because:
|
|
568
|
+
# - "repli-group" matches both commands via split matching
|
|
569
|
+
# - Common prefix "describe-" (9 chars) <= input "repli-group" (11 chars)
|
|
570
|
+
result = action_completer("repli-group", parsed_args)
|
|
571
|
+
|
|
572
|
+
# Should return all valid operations
|
|
573
|
+
expected = ["describe-global-replication-groups", "describe-replication-groups"]
|
|
574
|
+
assert result == expected
|
|
575
|
+
|
|
576
|
+
# Should have called warn with the matching operations
|
|
577
|
+
mock_warn.assert_called_once()
|
|
578
|
+
call_args = mock_warn.call_args[0][0]
|
|
579
|
+
assert "repli-group" in call_args
|
|
580
|
+
assert "describe-replication-groups" in call_args
|
|
581
|
+
assert "describe-global-replication-groups" in call_args
|
|
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
|
|
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
|