gtfs_df 0.11.1 → 0.13.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 70a7d05d5600baac0b36b21d0ebdd8fd0719cfe572844e44ae710c25fbf353f8
4
- data.tar.gz: df2b5f47cd19bfe213644dcda42b22778acf98d8f104bf71c4e231eaa4860bba
3
+ metadata.gz: 35a4116956b65a7dfdfd539af912bf67f590a2b99b35b3ea0bb62eafb2e60690
4
+ data.tar.gz: 14e43ea9a946392ac3044306a166544ae0183868b7305811be0c168fc6b9b153
5
5
  SHA512:
6
- metadata.gz: ee94819792b5c1389783a7844ce23af40de13264c5c069e608ae9c2383254c3fdcdc09915f5c33348d204675e5e373f8c26b039ff97144be37721aa7f4bc1b49
7
- data.tar.gz: 28f28bb69a62738c817dafbc36c756a96f56f9f96d4d587aab2b9dffa8df5b6948b25d6b024e70856639cc2a97c75c49ff510bb7695c825f54405bf23701582d
6
+ metadata.gz: 7c26d8662f454706e3ab4d641cb2f7f4f076acbd80fa9ce3689c2746bbec30d5c4added43129a7f984f6137bc1eee3a3d4597c1ea0d68b08d35caf79e3abe2c9
7
+ data.tar.gz: 824f21c6e8cabec24b31222ce9cb45ae71e0455edf5346ca56294e9490b5223915a782f0501f5800fc7f3aacd13ae1f5c6120012ffa7a9828ed6e55b9de93485
data/CHANGELOG.md CHANGED
@@ -1,3 +1,28 @@
1
+ ## [0.13.0] - 2026-10-07
2
+
3
+ ### 🚀 Features
4
+
5
+ - Add custom file processing to reader
6
+ - Add custom file processing to feed and graph
7
+ - Add custom file processing to writer
8
+
9
+ ### 💼 Other
10
+
11
+ - Devenv 2
12
+ ## [0.12.0] - 2026-06-12
13
+
14
+ ### 🚀 Features
15
+
16
+ - Allow loading a subset of tables
17
+
18
+ ### 🐛 Bug Fixes
19
+
20
+ - Truncate ragged lines
21
+ - Consider BOM when looking for empty files
22
+
23
+ ### ⚙️ Miscellaneous Tasks
24
+
25
+ - Bump version to 0.12.0
1
26
  ## [0.11.1] - 2026-04-28
2
27
 
3
28
  ### 🐛 Bug Fixes
@@ -8,6 +33,10 @@
8
33
 
9
34
  - Validate filtering from feed with empty calendar
10
35
  - Validate unreferenced calendar dates
36
+
37
+ ### ⚙️ Miscellaneous Tasks
38
+
39
+ - Bump version to 0.11.1
11
40
  ## [0.11.0] - 2026-03-30
12
41
 
13
42
  ### ⚙️ Miscellaneous Tasks
data/README.md CHANGED
@@ -35,6 +35,18 @@ feed = GtfsDf::Reader.load_from_zip('path/to/gtfs.zip')
35
35
  # Or, load from a directory
36
36
  feed = GtfsDf::Reader.load_from_dir('path/to/gtfs_dir')
37
37
 
38
+ # Load methods accept an 'extra' parameter that specifies custom files and how to build them
39
+ extra = {
40
+ files: %w[directions],
41
+ classes: {
42
+ directions: DirectionsClass,
43
+ },
44
+ graph_edges: [
45
+ ["routes", "directions", {dependencies: [{"directions" => "route_id", "routes" => "route_id"}], optional: true}]
46
+ ],
47
+ }
48
+ GtfsDf::Reader.load_from_zip('path/to/gtfs.zip', extra:)
49
+
38
50
  # Parse times as seconds since midnight instead of string
39
51
  feed = GtfsDf::Reader.load_from_dir('path/to/gtfs_dir', parse_times: true)
40
52
 
data/devenv.lock CHANGED
@@ -3,10 +3,11 @@
3
3
  "devenv": {
4
4
  "locked": {
5
5
  "dir": "src/modules",
6
- "lastModified": 1765397744,
6
+ "lastModified": 1783109842,
7
+ "narHash": "sha256-Fi7lW+sg1ChduOOQRBCK5JeA1cw4x5wSjIJcppRGGqQ=",
7
8
  "owner": "cachix",
8
9
  "repo": "devenv",
9
- "rev": "fd121886248781fee8546e4b6edc6d27e1d4efd5",
10
+ "rev": "e30eb49258bee68353bd9c619823f635f4afa86c",
10
11
  "type": "github"
11
12
  },
12
13
  "original": {
@@ -19,14 +20,15 @@
19
20
  "flake-compat": {
20
21
  "flake": false,
21
22
  "locked": {
22
- "lastModified": 1765121682,
23
- "owner": "edolstra",
23
+ "lastModified": 1767039857,
24
+ "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
25
+ "owner": "NixOS",
24
26
  "repo": "flake-compat",
25
- "rev": "65f23138d8d09a92e30f1e5c87611b23ef451bf3",
27
+ "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
26
28
  "type": "github"
27
29
  },
28
30
  "original": {
29
- "owner": "edolstra",
31
+ "owner": "NixOS",
30
32
  "repo": "flake-compat",
31
33
  "type": "github"
32
34
  }
@@ -34,10 +36,11 @@
34
36
  "flake-compat_2": {
35
37
  "flake": false,
36
38
  "locked": {
37
- "lastModified": 1765121682,
39
+ "lastModified": 1767039857,
40
+ "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
38
41
  "owner": "edolstra",
39
42
  "repo": "flake-compat",
40
- "rev": "65f23138d8d09a92e30f1e5c87611b23ef451bf3",
43
+ "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
41
44
  "type": "github"
42
45
  },
43
46
  "original": {
@@ -52,6 +55,7 @@
52
55
  },
53
56
  "locked": {
54
57
  "lastModified": 1731533236,
58
+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
55
59
  "owner": "numtide",
56
60
  "repo": "flake-utils",
57
61
  "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
@@ -66,16 +70,16 @@
66
70
  "git-hooks": {
67
71
  "inputs": {
68
72
  "flake-compat": "flake-compat",
69
- "gitignore": "gitignore",
70
73
  "nixpkgs": [
71
74
  "nixpkgs"
72
75
  ]
73
76
  },
74
77
  "locked": {
75
- "lastModified": 1765404074,
78
+ "lastModified": 1783008725,
79
+ "narHash": "sha256-jGiy6+sxjNWXSjp25uoJuNfyH9zBK1PEDY0lVoL4ibQ=",
76
80
  "owner": "cachix",
77
81
  "repo": "git-hooks.nix",
78
- "rev": "2d6f58930fbcd82f6f9fd59fb6d13e37684ca529",
82
+ "rev": "bca82caa46d5ec0f5d422c61fb1e30bc51313cbe",
79
83
  "type": "github"
80
84
  },
81
85
  "original": {
@@ -84,32 +88,16 @@
84
88
  "type": "github"
85
89
  }
86
90
  },
87
- "gitignore": {
91
+ "nixpkgs": {
88
92
  "inputs": {
89
- "nixpkgs": [
90
- "git-hooks",
91
- "nixpkgs"
92
- ]
93
+ "nixpkgs-src": "nixpkgs-src"
93
94
  },
94
95
  "locked": {
95
- "lastModified": 1762808025,
96
- "owner": "hercules-ci",
97
- "repo": "gitignore.nix",
98
- "rev": "cb5e3fdca1de58ccbc3ef53de65bd372b48f567c",
99
- "type": "github"
100
- },
101
- "original": {
102
- "owner": "hercules-ci",
103
- "repo": "gitignore.nix",
104
- "type": "github"
105
- }
106
- },
107
- "nixpkgs": {
108
- "locked": {
109
- "lastModified": 1764580874,
96
+ "lastModified": 1782924808,
97
+ "narHash": "sha256-tn2ahNv3ZNXyVHdPx/uw+N0xa7YSMtXUr6OEvu+s8ng=",
110
98
  "owner": "cachix",
111
99
  "repo": "devenv-nixpkgs",
112
- "rev": "dcf61356c3ab25f1362b4a4428a6d871e84f1d1d",
100
+ "rev": "e2c881cb8d5f1cac6cef9d71f0d450a55691b999",
113
101
  "type": "github"
114
102
  },
115
103
  "original": {
@@ -128,10 +116,11 @@
128
116
  ]
129
117
  },
130
118
  "locked": {
131
- "lastModified": 1765345660,
119
+ "lastModified": 1782898183,
120
+ "narHash": "sha256-2wRgS4D9TnY68y14I69FfYVLzcWXMnystgfGLkBuAMI=",
132
121
  "owner": "bobvanderlinden",
133
122
  "repo": "nixpkgs-ruby",
134
- "rev": "35e7b0919db2859f4022d1aa00b9c01253be1bce",
123
+ "rev": "edd32327ce414b547db75ca35eece1bd5329073c",
135
124
  "type": "github"
136
125
  },
137
126
  "original": {
@@ -140,20 +129,35 @@
140
129
  "type": "github"
141
130
  }
142
131
  },
132
+ "nixpkgs-src": {
133
+ "flake": false,
134
+ "locked": {
135
+ "lastModified": 1782636521,
136
+ "narHash": "sha256-OG8laCOGtkxlB1JH3XqOnXxnkGJme4mQdXo5hkhCQIY=",
137
+ "owner": "NixOS",
138
+ "repo": "nixpkgs",
139
+ "rev": "e1c1b84752fb0897897380a3cae9dc7fcab91ca3",
140
+ "type": "github"
141
+ },
142
+ "original": {
143
+ "owner": "NixOS",
144
+ "ref": "nixpkgs-unstable",
145
+ "repo": "nixpkgs",
146
+ "type": "github"
147
+ }
148
+ },
143
149
  "root": {
144
150
  "inputs": {
145
151
  "devenv": "devenv",
146
152
  "git-hooks": "git-hooks",
147
153
  "nixpkgs": "nixpkgs",
148
- "nixpkgs-ruby": "nixpkgs-ruby",
149
- "pre-commit-hooks": [
150
- "git-hooks"
151
- ]
154
+ "nixpkgs-ruby": "nixpkgs-ruby"
152
155
  }
153
156
  },
154
157
  "systems": {
155
158
  "locked": {
156
159
  "lastModified": 1681028828,
160
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
157
161
  "owner": "nix-systems",
158
162
  "repo": "default",
159
163
  "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
@@ -168,4 +172,4 @@
168
172
  },
169
173
  "root": "root",
170
174
  "version": 7
171
- }
175
+ }
data/devenv.nix CHANGED
@@ -7,6 +7,7 @@
7
7
  enable = true;
8
8
  versionFile = ./.ruby-version;
9
9
  bundler.enable = false;
10
+ lsp.enable = false;
10
11
  };
11
12
 
12
13
  git-hooks.hooks = {
data/devenv.yaml CHANGED
@@ -6,3 +6,8 @@ inputs:
6
6
  inputs:
7
7
  nixpkgs:
8
8
  follows: nixpkgs
9
+ git-hooks:
10
+ url: github:cachix/git-hooks.nix
11
+ inputs:
12
+ nixpkgs:
13
+ follows: nixpkgs
@@ -12,7 +12,13 @@ module GtfsDf
12
12
  elsif input.is_a?(String)
13
13
  # TODO: use `infer_schema: false` instead of `infer_schema_length` after polars release:
14
14
  # https://github.com/ankane/ruby-polars/blob/master/CHANGELOG.md#100-unreleased
15
- df = Polars.read_csv(input, infer_schema_length: 0, encoding: "utf8-lossy")
15
+ df = Polars
16
+ .read_csv(
17
+ input,
18
+ infer_schema_length: 0,
19
+ encoding: "utf8-lossy",
20
+ truncate_ragged_lines: true
21
+ )
16
22
  .rename(->(col) { col.strip })
17
23
 
18
24
  # Strip out empty lines. Unfortunately read_csv does not support the drop_empty_rows
data/lib/gtfs_df/feed.rb CHANGED
@@ -38,15 +38,17 @@ module GtfsDf
38
38
 
39
39
  attr_accessor(*GTFS_FILES)
40
40
  attr_accessor(:parse_times)
41
- attr_reader(:graph)
41
+ attr_reader(:graph, :extra)
42
42
 
43
43
  # Initialize with a hash of DataFrames
44
44
  REQUIRED_GTFS_FILES = %w[agency stops routes trips stop_times].freeze
45
45
 
46
46
  # @param data [Hash] Hash of DataFrames for each GTFS file
47
47
  # @param parse_times [Boolean] Whether to parse time fields to seconds since midnight (default: false)
48
- def initialize(data = {}, parse_times: false)
48
+ # @param extra [Hash] Extra file metadata to include in the feed; see README
49
+ def initialize(data = {}, parse_times: false, extra: {})
49
50
  @parse_times = parse_times
51
+ @extra = extra
50
52
 
51
53
  missing = REQUIRED_GTFS_FILES.reject { |file| data[file].is_a?(Polars::DataFrame) }
52
54
  # At least one of calendar or calendar_dates must be present
@@ -59,12 +61,16 @@ module GtfsDf
59
61
  end.join(", ")}"
60
62
  end
61
63
 
62
- @graph = GtfsDf::Graph.build
64
+ extra_files = @extra[:files] || []
65
+ extra_edges = @extra[:graph_edges] || []
66
+ extra_classes = @extra[:classes] || {}
67
+ @graph = GtfsDf::Graph.build(extra_edges:, extra_files:)
68
+ singleton_class.attr_accessor(*extra_files)
63
69
 
64
- GTFS_FILES.each do |file|
70
+ (GTFS_FILES + extra_files).each do |file|
65
71
  df = data[file]
66
- schema_class_name = file.split("_").map(&:capitalize).join
67
- schema_class = begin
72
+ schema_class = extra_classes[file.to_sym] || begin
73
+ schema_class_name = file.split("_").map(&:capitalize).join
68
74
  GtfsDf::Schema.const_get(schema_class_name)
69
75
  rescue
70
76
  nil
@@ -106,7 +112,7 @@ module GtfsDf
106
112
  def filter(view, filter_only_children: false)
107
113
  filtered = {}
108
114
 
109
- GTFS_FILES.each do |file|
115
+ (GTFS_FILES + (@extra[:files] || [])).each do |file|
110
116
  df = send(file)
111
117
  next unless df
112
118
 
@@ -145,7 +151,7 @@ module GtfsDf
145
151
 
146
152
  (!df || df.height == 0) && !is_required_file
147
153
  end
148
- self.class.new(filtered, parse_times: @parse_times)
154
+ self.class.new(filtered, parse_times: @parse_times, extra: @extra)
149
155
  end
150
156
 
151
157
  # Utility method that returns a hash of dataframes by file name
@@ -374,7 +380,10 @@ module GtfsDf
374
380
  # not dropped when another edge is processed first.
375
381
  def prune!(root, filtered, filter_only_children: false)
376
382
  seen_edges = Set.new
377
- rerooted_graph = Graph.build(bidirectional: !filter_only_children)
383
+ extra_files = @extra[:files] || []
384
+ extra_edges = @extra[:graph_edges] || []
385
+ all_nodes = Graph.all_nodes(extra_files:)
386
+ rerooted_graph = Graph.build(bidirectional: !filter_only_children, extra_edges:, extra_files:)
378
387
  accumulated_service_ids = Polars::Series.new("service_id", dtype: Polars::String)
379
388
  trips_base_df = nil
380
389
 
@@ -388,8 +397,8 @@ module GtfsDf
388
397
  next if seen_edges.include?(edge)
389
398
  seen_edges.add(edge)
390
399
 
391
- parent_node = Graph::NODES[parent_node_id]
392
- child_node = Graph::NODES[child_node_id]
400
+ parent_node = all_nodes[parent_node_id]
401
+ child_node = all_nodes[child_node_id]
393
402
  parent_df = filtered[parent_node.fetch(:file)]
394
403
  next unless parent_df
395
404
 
data/lib/gtfs_df/graph.rb CHANGED
@@ -40,10 +40,17 @@ module GtfsDf
40
40
 
41
41
  NODES = STANDARD_FILE_NODES.merge(STOP_NODES).freeze
42
42
 
43
+ def self.all_nodes(extra_files: [])
44
+ extra_nodes = extra_files.map do |file|
45
+ [file, {id: file, file: file, filter: nil}]
46
+ end.to_h
47
+ NODES.merge(extra_nodes)
48
+ end
49
+
43
50
  # Returns a directed graph of GTFS file dependencies
44
- def self.build(bidirectional: false)
51
+ def self.build(bidirectional: false, extra_edges: [], extra_files: [])
45
52
  g = NetworkX::DiGraph.new
46
- NODES.keys.each { |node| g.add_node(node) }
53
+ all_nodes(extra_files:).keys.each { |node| g.add_node(node) }
47
54
 
48
55
  # Edges should be parent, child
49
56
  # TODO: Add fare_rules -> stops + test
@@ -160,6 +167,7 @@ module GtfsDf
160
167
  {"booking_rules" => "booking_rule_id", "stop_times" => "drop_off_booking_rule_id"}
161
168
  ]}]
162
169
  ]
170
+ edges += extra_edges
163
171
 
164
172
  edges.each do |from, to, attrs|
165
173
  g.add_edge(from, to, **attrs)
@@ -6,13 +6,16 @@ module GtfsDf
6
6
  #
7
7
  # @param zip_path [String] Path to the GTFS zip file
8
8
  # @param parse_times [Boolean] Whether to parse time fields to seconds since midnight (default: false)
9
+ # @param relevant_files [Array<String>] A list of file names, useful to avoid loading tables you don't care about.
9
10
  # @return [Feed] The loaded GTFS feed
10
- def self.load_from_zip(zip_path, parse_times: false)
11
+ def self.load_from_zip(zip_path, parse_times: false, relevant_files: nil, extra: nil)
11
12
  data = nil
12
13
 
13
- relevant_files = GtfsDf::Feed::GTFS_FILES
14
- .map { |name| "#{name}.txt" }
15
- .to_set
14
+ relevant_files ||= GtfsDf::Feed::GTFS_FILES.map { |name| "#{name}.txt" }
15
+ relevant_files = relevant_files.to_set
16
+ extra ||= {}
17
+ extra_files = extra[:files] || []
18
+ extra_classes = extra[:classes] || {}
16
19
 
17
20
  seen = {}
18
21
 
@@ -27,9 +30,11 @@ module GtfsDf
27
30
  end
28
31
 
29
32
  # We're skipping:
30
- # - unrelated files
33
+ # - files neither relevant nor supported by extra
31
34
  # - empty feed files
32
- next unless relevant_files.include?(file_name) && has_header?(entry)
35
+ file_name_truncated = file_name.split(".").first # without the .txt extension
36
+ relevant_or_extra = relevant_files.include?(file_name) || (extra_files.include?(file_name_truncated) && extra_classes.key?(file_name_truncated.to_sym))
37
+ next unless relevant_or_extra && has_header?(entry)
33
38
 
34
39
  seen[file_name] = entry.name
35
40
 
@@ -37,7 +42,7 @@ module GtfsDf
37
42
  end
38
43
  end
39
44
 
40
- data = load_from_dir(tmpdir, parse_times: parse_times)
45
+ data = load_from_dir(tmpdir, parse_times:, relevant_files:, extra:)
41
46
  end
42
47
 
43
48
  data
@@ -47,26 +52,42 @@ module GtfsDf
47
52
  #
48
53
  # @param dir_path [String] Path to the GTFS directory
49
54
  # @param parse_times [Boolean] Whether to parse time fields to seconds since midnight (default: false)
55
+ # @param relevant_files [Array<String>] A list of file names, useful to avoid loading tables you don't care about.
50
56
  # @return [Feed] The loaded GTFS feed
51
- def self.load_from_dir(dir_path, parse_times: false)
57
+ def self.load_from_dir(dir_path, parse_times: false, relevant_files: nil, extra: nil)
58
+ relevant_files ||= GtfsDf::Feed::GTFS_FILES.map { |name| "#{name}.txt" }
59
+ relevant_files = relevant_files.to_set
60
+ extra ||= {}
61
+ extra_files = extra[:files] || []
62
+ extra_classes = extra[:classes] || {}
63
+
52
64
  data = {}
53
- GtfsDf::Feed::GTFS_FILES.each do |gtfs_file|
54
- path = File.join(dir_path, "#{gtfs_file}.txt")
55
- next unless File.exist?(path)
65
+ (GtfsDf::Feed::GTFS_FILES + extra_files).each do |gtfs_file|
66
+ basename = "#{gtfs_file}.txt"
67
+ path = File.join(dir_path, basename)
68
+ relevant_or_extra = relevant_files.include?(basename) || (extra_files.include?(gtfs_file) && extra_classes.key?(gtfs_file.to_sym))
69
+ next unless relevant_or_extra && File.exist?(path)
56
70
 
57
- data[gtfs_file] = data_frame(gtfs_file, path)
71
+ data[gtfs_file] = data_frame(gtfs_file, path, extra_classes)
58
72
  end
59
73
 
60
- GtfsDf::Feed.new(data, parse_times: parse_times)
74
+ GtfsDf::Feed.new(data, parse_times: parse_times, extra: extra)
61
75
  end
62
76
 
63
- private_class_method def self.data_frame(gtfs_file, path)
77
+ private_class_method def self.data_frame(gtfs_file, path, extra_classes)
78
+ custom_class = extra_classes[gtfs_file.to_sym]
79
+ return custom_class.new(path).df if custom_class
80
+
64
81
  schema_class_name = gtfs_file.split("_").map(&:capitalize).join
65
82
  GtfsDf::Schema.const_get(schema_class_name).new(path).df
66
83
  end
67
84
 
68
85
  private_class_method def self.has_header?(zip_entry)
69
- zip_entry.get_input_stream.readline.strip != ""
86
+ zip_entry
87
+ .get_input_stream
88
+ .readline
89
+ .delete_prefix("\xEF\xBB\xBF".b) # BOM
90
+ .strip != ""
70
91
  rescue
71
92
  false
72
93
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GtfsDf
4
- VERSION = "0.11.1"
4
+ VERSION = "0.13.0"
5
5
  end
@@ -11,13 +11,13 @@ module GtfsDf
11
11
  require "zlib"
12
12
  FileUtils.mkdir_p(File.dirname(zip_path))
13
13
  Zip::File.open(zip_path, create: true) do |zipfile|
14
- GtfsDf::Feed::GTFS_FILES.each do |file|
14
+ output_files(feed).each do |file|
15
15
  df = feed.send(file)
16
16
  next unless df.is_a?(Polars::DataFrame)
17
17
 
18
18
  # Convert time fields back to strings if parse_times was enabled
19
19
  if feed.parse_times
20
- df = format_time_fields(file, df)
20
+ df = format_time_fields(file, df, feed)
21
21
  end
22
22
 
23
23
  # Write CSV to StringIO
@@ -37,26 +37,34 @@ module GtfsDf
37
37
  # @param dir_path [String] The path where the directory will be created
38
38
  def self.write_to_dir(feed, dir_path)
39
39
  FileUtils.mkdir_p(dir_path)
40
- GtfsDf::Feed::GTFS_FILES.each do |file|
40
+ output_files(feed).each do |file|
41
41
  df = feed.send(file)
42
42
  next unless df.is_a?(Polars::DataFrame)
43
43
 
44
44
  # Convert time fields back to strings if parse_times was enabled
45
- df = format_time_fields(file, df) if feed.parse_times
45
+ df = format_time_fields(file, df, feed) if feed.parse_times
46
46
 
47
47
  # Write CSV directly to file
48
48
  df.write_csv(File.join(dir_path, "#{file}.txt"))
49
49
  end
50
50
  end
51
51
 
52
+ def self.output_files(feed)
53
+ extra_files = feed.extra&.[](:files) || []
54
+ GtfsDf::Feed::GTFS_FILES + extra_files
55
+ end
56
+ private_class_method :output_files
57
+
52
58
  # Formats time fields back to HH:MM:SS strings for a given GTFS file
53
59
  #
54
60
  # @param file [String] The GTFS file name (e.g., "stop_times")
55
61
  # @param df [Polars::DataFrame] The DataFrame to format
56
62
  # @return [Polars::DataFrame] DataFrame with time fields formatted as strings
57
- def self.format_time_fields(file, df)
58
- schema_class_name = file.split("_").map(&:capitalize).join
59
- schema_class = begin
63
+ def self.format_time_fields(file, df, feed = nil)
64
+ extra_classes = feed&.extra&.[](:classes) || {}
65
+ schema_class = extra_classes[file.to_sym]
66
+ schema_class ||= begin
67
+ schema_class_name = file.split("_").map(&:capitalize).join
60
68
  GtfsDf::Schema.const_get(schema_class_name)
61
69
  rescue
62
70
  nil
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gtfs_df
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.1
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Mejorado
@@ -74,7 +74,6 @@ files:
74
74
  - ".rspec"
75
75
  - ".rubocop.yml"
76
76
  - ".ruby-version"
77
- - ".solargraph.yml"
78
77
  - ".standard.yml"
79
78
  - CHANGELOG.md
80
79
  - LICENSE.txt
data/.solargraph.yml DELETED
@@ -1,26 +0,0 @@
1
- ---
2
- include:
3
- - Rakefile
4
- - Gemfile
5
- - "*.gemspec"
6
- - "**/*.rb"
7
- exclude:
8
- - spec/**/*
9
- - test/**/*
10
- - vendor/**/*
11
- - ".bundle/**/*"
12
- require: []
13
- domains: []
14
- reporters:
15
- - rubocop
16
- - require_not_found
17
- formatter:
18
- rubocop:
19
- cops: safe
20
- except: []
21
- only: []
22
- extra_args: []
23
- lint: true
24
- require_paths: []
25
- plugins: []
26
- max_files: 5000