From 5f0c06b23c5213cf7b07a6889645caef43794e01 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 8 Sep 2026 08:29:59 +0000 Subject: [PATCH] Fix SubscriptionText TSV parsing Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01CbtimCQAVdGhaEFXsEGKPK --- doc/PLUGINS.md | 9 ++- doc/VERSIONS | 1 + plugins/subscription/text.rb | 17 +++-- spec/plugins/subscription/text_spec.rb | 90 +++++++++++++++++++++++++- test/integration/test_text2feed.yml | 2 - 5 files changed, 109 insertions(+), 10 deletions(-) diff --git a/doc/PLUGINS.md b/doc/PLUGINS.md index 9ef8cb22..f785e835 100644 --- a/doc/PLUGINS.md +++ b/doc/PLUGINS.md @@ -669,10 +669,13 @@ no network, which makes it the plugin to test a Recipe's later half with. | `titles` | sequence | One item per title, no link | | `urls` | sequence | One item per URL, no title | | `feeds` | sequence | Mappings of `title`, `url`, `description`, `author`, `comments` | -| `files` | sequence | TSV paths; columns are title, url, description, author, comments | +| `files` | sequence | UTF-8 TSV paths; positional columns are title, url, description, author, comments | -The TSV separator is a tab, the file is read as UTF-8, and `~` is expanded. Any -combination of the four keys may be given. +The TSV separator is a tab and `~` is expanded. Empty columns supply no value +for that field without shifting later columns; blank or all-empty rows produce +no item. Only the first five columns are used. Line endings are removed before +splitting, but other field whitespace is preserved. Any combination of the four +keys may be given. #### SubscriptionTumblr — **Supported (external)** diff --git a/doc/VERSIONS b/doc/VERSIONS index ffd5e7be..c171bef8 100644 --- a/doc/VERSIONS +++ b/doc/VERSIONS @@ -9,6 +9,7 @@ v26.09 (Release Date: TBD) dependency from load failures, and preflight-validate Recipe plugins. - Preserve nil-link pipeline items and remove SubscriptionText placeholder title and link values. +- Preserve SubscriptionText TSV column positions and ignore empty input rows. v26.08 (2026-08-22) ------------------- diff --git a/plugins/subscription/text.rb b/plugins/subscription/text.rb index ef8a353d..5333324b 100644 --- a/plugins/subscription/text.rb +++ b/plugins/subscription/text.rb @@ -5,7 +5,7 @@ # License:: The GPL version 3, or LGPL version 3 (Dual License). # Contact:: idnanashi@gmail.com # Created:: May 6, 2013 -# Updated:: Aug 15, 2026 +# Updated:: Sep 8, 2026 # Copyright:: Copyright (c) 2012-2026 Automatic Ruby Developers. module Automatic::Plugin @@ -41,13 +41,22 @@ def feeds Array(@config['feeds']).map { |feed| Automatic::FeedMaker.generate_feed(feed) } end - # Tab separated, read as UTF-8, and `~` expanded. + # Tab separated, read as UTF-8, and `~` expanded. Empty columns stay in + # position and an all-empty row produces no item. def files Array(@config['files']).flat_map do |path| - File.foreach(File.expand_path(path), encoding: 'UTF-8').map do |line| - Automatic::FeedMaker.generate_feed(COLUMNS.zip(line.strip.split("\t")).to_h) + File.foreach(File.expand_path(path), encoding: 'UTF-8').filter_map do |line| + fields = tsv_fields(line) + Automatic::FeedMaker.generate_feed(fields) unless fields.nil? end end end + + def tsv_fields(line) + values = line.chomp.split("\t", -1).first(COLUMNS.length) + fields = COLUMNS.zip(values).to_h + fields.delete_if { |_field, value| value.nil? || value.empty? } + fields unless fields.empty? + end end end diff --git a/spec/plugins/subscription/text_spec.rb b/spec/plugins/subscription/text_spec.rb index 93db206b..ee4b87fc 100644 --- a/spec/plugins/subscription/text_spec.rb +++ b/spec/plugins/subscription/text_spec.rb @@ -5,7 +5,7 @@ # License:: The GPL version 3, or LGPL version 3 (Dual License). # Contact:: idnanashi@gmail.com # Created:: May 6, 2013 -# Updated:: Sep 7, 2026 +# Updated:: Sep 8, 2026 # Copyright:: Copyright (c) 2012-2026 Automatic Ruby Developers. require File.expand_path(File.dirname(__FILE__) + '../../../spec_helper') @@ -13,6 +13,16 @@ require 'subscription/text' describe Automatic::Plugin::SubscriptionText do + def items_from_tsv(contents) + Dir.mktmpdir('automatic-subscription-text') do |dir| + path = File.join(dir, 'input.tsv') + File.write(path, contents, encoding: 'UTF-8') + Automatic::Plugin::SubscriptionText.new( + { 'files' => [path] } + ).run.flat_map(&:items) + end + end + context "with empty titles" do subject { Automatic::Plugin::SubscriptionText.new( @@ -112,4 +122,82 @@ its(:run) { should have(1).feed } end + + it "builds a title-only item from a one-column TSV row" do + items = items_from_tsv("Title only\n") + + items.should have(1).item + items.first.title.should == "Title only" + items.first.link.should be_nil + end + + it "preserves a leading empty title column in a URL-only TSV row" do + items = items_from_tsv("\thttps://example.com/\r\n") + + items.should have(1).item + items.first.title.should be_nil + items.first.link.should == "https://example.com/" + end + + it "does not shift comments across an empty author column" do + items = items_from_tsv( + "Title\thttps://example.com/\tDescription\t\tComment\n" + ) + item = items.first + + items.should have(1).item + item.title.should == "Title" + item.link.should == "https://example.com/" + item.description.should == "Description" + item.author.should == "" + item.comments.should == "Comment" + end + + it "accepts trailing empty TSV fields without changing earlier columns" do + items = items_from_tsv( + "Title\thttps://example.com/\tDescription\t\t\n" + ) + item = items.first + + items.should have(1).item + item.title.should == "Title" + item.link.should == "https://example.com/" + item.description.should == "Description" + item.author.should == "" + item.comments.should == "" + end + + it "ignores blank and all-empty TSV rows" do + items = items_from_tsv( + "\n\t\t\t\t\nKept\thttps://example.com/\n" + ) + + items.should have(1).item + items.first.title.should == "Kept" + items.first.link.should == "https://example.com/" + end + + it "ignores TSV columns after comments" do + items = items_from_tsv( + "Title\thttps://example.com/\tDescription\tAuthor\tComment\tExtra\tMore\n" + ) + item = items.first + + items.should have(1).item + item.title.should == "Title" + item.link.should == "https://example.com/" + item.description.should == "Description" + item.author.should == "Author" + item.comments.should == "Comment" + end + + it "preserves field whitespace other than the line ending" do + items = items_from_tsv( + " Title \thttps://example.com/\t Description \n" + ) + item = items.first + + item.title.should == " Title " + item.description.should == " Description " + end end diff --git a/test/integration/test_text2feed.yml b/test/integration/test_text2feed.yml index 819097e4..6386c925 100644 --- a/test/integration/test_text2feed.yml +++ b/test/integration/test_text2feed.yml @@ -22,8 +22,6 @@ plugins: description: 'aaa' comments: 'bbb' author: 'ccc' - files: - - 'spec/fixtures/sampleFeeds.tsv' files: - 'spec/fixtures/sampleFeeds.tsv' - 'spec/fixtures/sampleFeeds2.tsv'