fastgpx 0.2.2__tar.gz → 0.4.0__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.
- {fastgpx-0.2.2 → fastgpx-0.4.0}/PKG-INFO +1 -1
- {fastgpx-0.2.2 → fastgpx-0.4.0}/docs/source/conf.py +43 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/pyproject.toml +1 -1
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/cpp/fastgpx/fastgpx.cpp +15 -1
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/cpp/fastgpx/fastgpx.hpp +3 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/cpp/fastgpx/fastgpx_test.cpp +6 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/cpp/python_fastgpx.cpp +8 -1
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/cpp/python_utc_chrono.hpp +13 -1
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/fastgpx/__init__.pyi +1 -1
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/fastgpx/fastgpx/__init__.pyi +9 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/tests/test_fastgpx.py +19 -1
- fastgpx-0.4.0/tests/test_time_bounds.py +73 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/uv.lock +1 -1
- {fastgpx-0.2.2 → fastgpx-0.4.0}/.clang-format +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/.gitignore +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/.python-version +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/CMakeLists.txt +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/CMakeSettings.json +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/Development.md +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/GPX.md +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/LICENSE.md +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/README.md +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/benchmarks/benchmark_gpx.py +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/benchmarks/benchmark_polyline.py +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/benchmarks/gpx_parse.md +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/catch2.py +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/coverage.bat +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/datetime.md +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/docs/Makefile +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/docs/make.bat +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/docs/source/api.rst +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/docs/source/index.rst +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/docs/source/overview.rst +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/profiling/profile_polyline.py +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/pytest.ini +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/cpp/CMakeLists.txt +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/cpp/app.cpp +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/cpp/expected_gpx_data.json +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/cpp/fastgpx/datetime.cpp +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/cpp/fastgpx/datetime.hpp +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/cpp/fastgpx/datetime_test.cpp +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/cpp/fastgpx/errors.cpp +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/cpp/fastgpx/errors.hpp +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/cpp/fastgpx/errors_test.cpp +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/cpp/fastgpx/filesystem.cpp +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/cpp/fastgpx/filesystem.hpp +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/cpp/fastgpx/filesystem_test.cpp +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/cpp/fastgpx/geom.cpp +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/cpp/fastgpx/geom.hpp +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/cpp/fastgpx/geom_test.cpp +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/cpp/fastgpx/polyline.cpp +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/cpp/fastgpx/polyline.hpp +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/cpp/fastgpx/test_data.cpp +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/cpp/fastgpx/test_data.hpp +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/cpp/fastgpx/test_data.json +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/cpp/fastgpx/test_data_test.cpp +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/fastgpx/__init__.py +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/fastgpx/fastgpx/geo.pyi +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/fastgpx/fastgpx/polyline.pyi +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/src/fastgpx/py.typed +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/tests/test_bounds.py +0 -0
- {fastgpx-0.2.2 → fastgpx-0.4.0}/tests/test_polyline.py +0 -0
@@ -3,9 +3,12 @@
|
|
3
3
|
# For the full list of built-in configuration values, see the documentation:
|
4
4
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
5
5
|
|
6
|
+
import re
|
6
7
|
import os
|
7
8
|
import sys
|
8
9
|
import tomllib
|
10
|
+
import logging as std_logging
|
11
|
+
from sphinx.util import logging
|
9
12
|
|
10
13
|
PROJECT_ROOT = os.path.abspath('../../')
|
11
14
|
|
@@ -49,3 +52,43 @@ autosummary_generate = True
|
|
49
52
|
|
50
53
|
html_theme = 'sphinx_rtd_theme'
|
51
54
|
html_static_path = ['_static']
|
55
|
+
|
56
|
+
|
57
|
+
# -- Adjust formatting for overloaded methods --------------------------------
|
58
|
+
|
59
|
+
logger = logging.getLogger(__name__)
|
60
|
+
|
61
|
+
|
62
|
+
def format_overloaded_docstring(app, what, name, obj, options, lines):
|
63
|
+
logger.info(f"[fastgpx-doc] Processing docstring for {name}")
|
64
|
+
if not lines:
|
65
|
+
return
|
66
|
+
|
67
|
+
logger.info(f"[fastgpx-doc] First line: {lines[0]}")
|
68
|
+
if lines[0].strip().startswith("Overloaded function"):
|
69
|
+
logger.info(f"[fastgpx-doc] Detected overloaded function in {name}")
|
70
|
+
|
71
|
+
new_lines = [".. rubric:: Overloads", ""]
|
72
|
+
|
73
|
+
for line in lines[1:]:
|
74
|
+
match = re.match(r"^\d+\.\s+(.*)", line.strip())
|
75
|
+
if match:
|
76
|
+
new_lines.extend([
|
77
|
+
".. code-block:: python",
|
78
|
+
"",
|
79
|
+
f" {match.group(1)}",
|
80
|
+
""
|
81
|
+
])
|
82
|
+
|
83
|
+
lines.clear()
|
84
|
+
lines.extend(new_lines)
|
85
|
+
|
86
|
+
|
87
|
+
def setup(app):
|
88
|
+
if app.verbosity >= 1:
|
89
|
+
logger.setLevel(std_logging.INFO)
|
90
|
+
else:
|
91
|
+
logger.setLevel(std_logging.WARNING)
|
92
|
+
|
93
|
+
logger.info("[fastgpx-doc] Setting up overloaded docstring formatting")
|
94
|
+
app.connect("autodoc-process-docstring", format_overloaded_docstring)
|
@@ -43,10 +43,14 @@ std::chrono::system_clock::time_point TimePoint::value() const
|
|
43
43
|
|
44
44
|
bool TimeBounds::IsEmpty() const
|
45
45
|
{
|
46
|
-
assert(start_time.has_value() == end_time.has_value());
|
47
46
|
return !start_time.has_value() && !end_time.has_value();
|
48
47
|
}
|
49
48
|
|
49
|
+
bool TimeBounds::IsRange() const
|
50
|
+
{
|
51
|
+
return start_time.has_value() && end_time.has_value();
|
52
|
+
}
|
53
|
+
|
50
54
|
void TimeBounds::Add(const std::chrono::system_clock::time_point time_point)
|
51
55
|
{
|
52
56
|
if (start_time.has_value())
|
@@ -380,6 +384,16 @@ Gpx ParseGpx(const std::filesystem::path& path)
|
|
380
384
|
pugi::xml_node root = doc.child("gpx");
|
381
385
|
Gpx gpx;
|
382
386
|
|
387
|
+
const auto metadata = root.child("metadata");
|
388
|
+
if (metadata)
|
389
|
+
{
|
390
|
+
const auto name = metadata.child("name");
|
391
|
+
if (name)
|
392
|
+
{
|
393
|
+
gpx.name.emplace(name.text().as_string());
|
394
|
+
}
|
395
|
+
}
|
396
|
+
|
383
397
|
// Iterate over each <trk> element
|
384
398
|
for (pugi::xml_node track = root.child("trk"); track; track = track.next_sibling("trk"))
|
385
399
|
{
|
@@ -28,6 +28,7 @@ struct TimeBounds
|
|
28
28
|
std::optional<std::chrono::system_clock::time_point> end_time = std::nullopt;
|
29
29
|
|
30
30
|
bool IsEmpty() const;
|
31
|
+
bool IsRange() const;
|
31
32
|
|
32
33
|
void Add(std::chrono::system_clock::time_point time_point);
|
33
34
|
void Add(const TimeBounds& time_bounds);
|
@@ -111,6 +112,8 @@ private:
|
|
111
112
|
struct Gpx
|
112
113
|
{
|
113
114
|
// <metadata>
|
115
|
+
std::optional<std::string> name; // <name>
|
116
|
+
|
114
117
|
// <wpt>
|
115
118
|
// <tre>
|
116
119
|
std::vector<Track> tracks; // <trk>
|
@@ -33,6 +33,8 @@ TEST_CASE("Parse two-point single segment track", "[parse][simple]")
|
|
33
33
|
REQUIRE(gpx.tracks.size() == 1);
|
34
34
|
REQUIRE(gpx.tracks[0].segments.size() == 1);
|
35
35
|
|
36
|
+
CHECK(!gpx.name.has_value());
|
37
|
+
|
36
38
|
CHECK_THAT(gpx.tracks[0].segments[0].GetLength2D(), WithinAbs(1.3839, kMETERS_TOL));
|
37
39
|
CHECK_THAT(gpx.tracks[0].GetLength2D(), WithinAbs(1.3839, kMETERS_TOL));
|
38
40
|
CHECK_THAT(gpx.GetLength2D(), WithinAbs(1.3839, kMETERS_TOL));
|
@@ -74,6 +76,8 @@ TEST_CASE("Parse time bounds of real world GPX file", "[parse][simple]")
|
|
74
76
|
"gpx/2024 TopCamp/Connected_20240529_091916_Harald_Bothners_Veg_36_7052_Trondheim.gpx";
|
75
77
|
const auto gpx = fastgpx::ParseGpx(path);
|
76
78
|
|
79
|
+
CHECK(gpx.name.value() == "Harald Bothners Veg 36, 7052 Trondheim");
|
80
|
+
|
77
81
|
REQUIRE(gpx.tracks.size() == 1);
|
78
82
|
|
79
83
|
// TimeBounds
|
@@ -97,6 +101,8 @@ TEST_CASE("Parse bounds of real world GPX file", "[parse][simple]")
|
|
97
101
|
"gpx/2024 TopCamp/Connected_20240529_091916_Harald_Bothners_Veg_36_7052_Trondheim.gpx";
|
98
102
|
const auto gpx = fastgpx::ParseGpx(path);
|
99
103
|
|
104
|
+
CHECK(gpx.name.value() == "Harald Bothners Veg 36, 7052 Trondheim");
|
105
|
+
|
100
106
|
REQUIRE(gpx.tracks.size() == 1);
|
101
107
|
|
102
108
|
const fastgpx::LatLong expected_min(61.841507, 9.090457);
|
@@ -53,7 +53,13 @@ PYBIND11_MODULE(fastgpx, m)
|
|
53
53
|
py::arg("end_time"))
|
54
54
|
.def_readwrite("start_time", &fastgpx::TimeBounds::start_time)
|
55
55
|
.def_readwrite("end_time", &fastgpx::TimeBounds::end_time)
|
56
|
-
.def("is_empty", &fastgpx::TimeBounds::IsEmpty)
|
56
|
+
.def("is_empty", &fastgpx::TimeBounds::IsEmpty)
|
57
|
+
.def("is_range", &fastgpx::TimeBounds::IsRange)
|
58
|
+
.def("add",
|
59
|
+
py::overload_cast<std::chrono::system_clock::time_point>(&fastgpx::TimeBounds::Add),
|
60
|
+
py::arg("datetime"))
|
61
|
+
.def("add", py::overload_cast<const fastgpx::TimeBounds&>(&fastgpx::TimeBounds::Add),
|
62
|
+
py::arg("timebounds"));
|
57
63
|
|
58
64
|
py::class_<fastgpx::LatLong>(m, "LatLong")
|
59
65
|
.def(py::init<>())
|
@@ -197,6 +203,7 @@ PYBIND11_MODULE(fastgpx, m)
|
|
197
203
|
py::class_<fastgpx::Gpx>(m, "Gpx")
|
198
204
|
.def(py::init<>()) // Default constructor
|
199
205
|
.def_readwrite("tracks", &fastgpx::Gpx::tracks)
|
206
|
+
.def_readwrite("name", &fastgpx::Gpx::name)
|
200
207
|
.def("bounds", &fastgpx::Gpx::GetBounds)
|
201
208
|
.def("get_bounds", &fastgpx::Gpx::GetBounds) // gpxpy compatiblity
|
202
209
|
.def("time_bounds", &fastgpx::Gpx::GetTimeBounds)
|
@@ -27,6 +27,15 @@
|
|
27
27
|
namespace PYBIND11_NAMESPACE {
|
28
28
|
namespace detail {
|
29
29
|
|
30
|
+
inline std::time_t to_utc_time_t(std::tm* tm)
|
31
|
+
{
|
32
|
+
#if defined(_WIN32)
|
33
|
+
return _mkgmtime(tm);
|
34
|
+
#else
|
35
|
+
return timegm(tm);
|
36
|
+
#endif
|
37
|
+
}
|
38
|
+
|
30
39
|
template<typename type>
|
31
40
|
class duration_caster
|
32
41
|
{
|
@@ -195,7 +204,10 @@ public:
|
|
195
204
|
return false;
|
196
205
|
}
|
197
206
|
|
198
|
-
|
207
|
+
// HACK: Assume datetime.datetime objects are in UTC, so we use timegm instead of mktime.
|
208
|
+
// value = time_point_cast<Duration>(system_clock::from_time_t(std::mktime(&cal)) + msecs);
|
209
|
+
std::time_t tt = to_utc_time_t(&cal);
|
210
|
+
value = time_point_cast<Duration>(system_clock::from_time_t(tt) + msecs);
|
199
211
|
return true;
|
200
212
|
}
|
201
213
|
|
@@ -9,4 +9,4 @@ from fastgpx.fastgpx import geo
|
|
9
9
|
from fastgpx.fastgpx import parse
|
10
10
|
from fastgpx.fastgpx import polyline
|
11
11
|
from . import fastgpx
|
12
|
-
__all__ = ['Bounds', 'Gpx', 'LatLong', 'Segment', 'TimeBounds', 'Track', '
|
12
|
+
__all__: list = ['Bounds', 'Gpx', 'LatLong', 'Segment', 'TimeBounds', 'Track', 'geo', 'parse', 'polyline']
|
@@ -51,6 +51,7 @@ class Bounds:
|
|
51
51
|
def min_longitude(self, arg1: float) -> None:
|
52
52
|
...
|
53
53
|
class Gpx:
|
54
|
+
name: str | None
|
54
55
|
tracks: list[Track]
|
55
56
|
def __init__(self) -> None:
|
56
57
|
...
|
@@ -101,8 +102,16 @@ class TimeBounds:
|
|
101
102
|
@typing.overload
|
102
103
|
def __init__(self, start_time: datetime.datetime | None, end_time: datetime.datetime | None) -> None:
|
103
104
|
...
|
105
|
+
@typing.overload
|
106
|
+
def add(self, datetime: datetime.datetime) -> None:
|
107
|
+
...
|
108
|
+
@typing.overload
|
109
|
+
def add(self, timebounds: TimeBounds) -> None:
|
110
|
+
...
|
104
111
|
def is_empty(self) -> bool:
|
105
112
|
...
|
113
|
+
def is_range(self) -> bool:
|
114
|
+
...
|
106
115
|
class Track:
|
107
116
|
comment: str | None
|
108
117
|
description: str | None
|
@@ -1,7 +1,6 @@
|
|
1
1
|
import datetime
|
2
2
|
|
3
3
|
import gpxpy
|
4
|
-
import gpxpy.gpx
|
5
4
|
import pytest
|
6
5
|
|
7
6
|
import fastgpx
|
@@ -53,6 +52,25 @@ def test_segment_length2d(gpx_path: str):
|
|
53
52
|
distance = gpx.tracks[0].segments[0].length_2d()
|
54
53
|
assert distance == pytest.approx(17809.2701, abs=METERS_TOL)
|
55
54
|
|
55
|
+
# fastgpx.Gpx.name
|
56
|
+
|
57
|
+
|
58
|
+
def test_gpx_name_missing():
|
59
|
+
path = 'gpx/test/debug-segment.gpx'
|
60
|
+
gpx = fastgpx.parse(path)
|
61
|
+
assert gpx.name is None
|
62
|
+
|
63
|
+
|
64
|
+
def test_gpx_name_empty_string(gpx_path: str):
|
65
|
+
gpx = fastgpx.parse(gpx_path)
|
66
|
+
assert gpx.name == ''
|
67
|
+
|
68
|
+
|
69
|
+
def test_gpx_name():
|
70
|
+
path = 'gpx/test/two-points.gpx'
|
71
|
+
gpx = fastgpx.parse(path)
|
72
|
+
assert gpx.name == 'Two Point Segment'
|
73
|
+
|
56
74
|
# fastgpx.Gpx.bounds
|
57
75
|
|
58
76
|
|
@@ -0,0 +1,73 @@
|
|
1
|
+
from datetime import datetime
|
2
|
+
|
3
|
+
import fastgpx
|
4
|
+
|
5
|
+
|
6
|
+
class TestTimeBounds:
|
7
|
+
|
8
|
+
def test_init_defaults(self):
|
9
|
+
bounds = fastgpx.TimeBounds()
|
10
|
+
assert bounds.is_empty()
|
11
|
+
assert not bounds.is_range()
|
12
|
+
assert bounds.start_time is None
|
13
|
+
assert bounds.end_time is None
|
14
|
+
|
15
|
+
def test_init_with_start_time(self):
|
16
|
+
start_time = datetime.fromisoformat("2025-06-20 08:07:28+00:00")
|
17
|
+
bounds = fastgpx.TimeBounds(start_time=start_time, end_time=None)
|
18
|
+
assert not bounds.is_empty()
|
19
|
+
assert not bounds.is_range()
|
20
|
+
assert bounds.start_time == start_time
|
21
|
+
assert bounds.end_time is None
|
22
|
+
|
23
|
+
def test_init_with_end_time(self):
|
24
|
+
end_time = datetime.fromisoformat("2025-06-27 13:37:00+00:00")
|
25
|
+
bounds = fastgpx.TimeBounds(start_time=None, end_time=end_time)
|
26
|
+
assert not bounds.is_empty()
|
27
|
+
assert not bounds.is_range()
|
28
|
+
assert bounds.start_time is None
|
29
|
+
assert bounds.end_time == end_time
|
30
|
+
|
31
|
+
def test_init_with_start_and_end_time(self):
|
32
|
+
start_time = datetime.fromisoformat("2025-06-20 08:07:28+00:00")
|
33
|
+
end_time = datetime.fromisoformat("2025-06-27 13:37:00+00:00")
|
34
|
+
bounds = fastgpx.TimeBounds(start_time=start_time, end_time=end_time)
|
35
|
+
assert not bounds.is_empty()
|
36
|
+
assert bounds.is_range()
|
37
|
+
assert bounds.start_time == start_time
|
38
|
+
assert bounds.end_time == end_time
|
39
|
+
|
40
|
+
def test_add_single_time_point(self):
|
41
|
+
bounds = fastgpx.TimeBounds()
|
42
|
+
time_point = datetime.fromisoformat("2025-06-20 08:07:28+00:00")
|
43
|
+
bounds.add(time_point)
|
44
|
+
assert not bounds.is_empty()
|
45
|
+
assert bounds.is_range()
|
46
|
+
assert bounds.start_time == time_point
|
47
|
+
assert bounds.end_time == time_point
|
48
|
+
|
49
|
+
def test_add_second_time_point(self):
|
50
|
+
bounds = fastgpx.TimeBounds()
|
51
|
+
time_point1 = datetime.fromisoformat("2025-06-20 08:07:28+00:00")
|
52
|
+
time_point2 = datetime.fromisoformat("2025-06-27 13:37:00+00:00")
|
53
|
+
bounds.add(time_point1)
|
54
|
+
bounds.add(time_point2)
|
55
|
+
assert not bounds.is_empty()
|
56
|
+
assert bounds.is_range()
|
57
|
+
assert bounds.start_time == time_point1
|
58
|
+
assert bounds.end_time == time_point2
|
59
|
+
|
60
|
+
def test_add_time_bounds(self):
|
61
|
+
bounds1 = fastgpx.TimeBounds(
|
62
|
+
start_time=datetime.fromisoformat("2025-06-20 08:07:28+00:00"),
|
63
|
+
end_time=datetime.fromisoformat("2025-06-27 13:37:00+00:00")
|
64
|
+
)
|
65
|
+
bounds2 = fastgpx.TimeBounds(
|
66
|
+
start_time=datetime.fromisoformat("2025-06-25 10:00:00+00:00"),
|
67
|
+
end_time=datetime.fromisoformat("2025-06-30 15:00:00+00:00")
|
68
|
+
)
|
69
|
+
bounds1.add(bounds2)
|
70
|
+
assert not bounds1.is_empty()
|
71
|
+
assert bounds1.is_range()
|
72
|
+
assert bounds1.start_time == bounds1.start_time
|
73
|
+
assert bounds1.end_time == bounds2.end_time
|
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
|