pdf-section-binding 1.0.0__tar.gz → 1.0.1__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 (22) hide show
  1. pdf_section_binding-1.0.1/MANIFEST.in +53 -0
  2. {pdf_section_binding-1.0.0/src/pdf_section_binding.egg-info → pdf_section_binding-1.0.1}/PKG-INFO +2 -2
  3. {pdf_section_binding-1.0.0 → pdf_section_binding-1.0.1}/pyproject.toml +2 -2
  4. {pdf_section_binding-1.0.0 → pdf_section_binding-1.0.1}/src/pdf_section_binding/version.py +1 -1
  5. {pdf_section_binding-1.0.0 → pdf_section_binding-1.0.1/src/pdf_section_binding.egg-info}/PKG-INFO +2 -2
  6. {pdf_section_binding-1.0.0 → pdf_section_binding-1.0.1}/src/pdf_section_binding.egg-info/SOURCES.txt +1 -3
  7. pdf_section_binding-1.0.0/MANIFEST.in +0 -15
  8. pdf_section_binding-1.0.0/tests/test-data/.gitkeep +0 -2
  9. pdf_section_binding-1.0.0/tests/test_section_binding.py +0 -296
  10. {pdf_section_binding-1.0.0 → pdf_section_binding-1.0.1}/LICENSE +0 -0
  11. {pdf_section_binding-1.0.0 → pdf_section_binding-1.0.1}/README.md +0 -0
  12. {pdf_section_binding-1.0.0 → pdf_section_binding-1.0.1}/requirements.txt +0 -0
  13. {pdf_section_binding-1.0.0 → pdf_section_binding-1.0.1}/setup.cfg +0 -0
  14. {pdf_section_binding-1.0.0 → pdf_section_binding-1.0.1}/setup.py +0 -0
  15. {pdf_section_binding-1.0.0 → pdf_section_binding-1.0.1}/src/pdf_section_binding/__init__.py +0 -0
  16. {pdf_section_binding-1.0.0 → pdf_section_binding-1.0.1}/src/pdf_section_binding/cli.py +0 -0
  17. {pdf_section_binding-1.0.0 → pdf_section_binding-1.0.1}/src/pdf_section_binding/core.py +0 -0
  18. {pdf_section_binding-1.0.0 → pdf_section_binding-1.0.1}/src/pdf_section_binding.egg-info/dependency_links.txt +0 -0
  19. {pdf_section_binding-1.0.0 → pdf_section_binding-1.0.1}/src/pdf_section_binding.egg-info/entry_points.txt +0 -0
  20. {pdf_section_binding-1.0.0 → pdf_section_binding-1.0.1}/src/pdf_section_binding.egg-info/not-zip-safe +0 -0
  21. {pdf_section_binding-1.0.0 → pdf_section_binding-1.0.1}/src/pdf_section_binding.egg-info/requires.txt +0 -0
  22. {pdf_section_binding-1.0.0 → pdf_section_binding-1.0.1}/src/pdf_section_binding.egg-info/top_level.txt +0 -0
@@ -0,0 +1,53 @@
1
+ include README.md
2
+ include LICENSE
3
+ include pyproject.toml
4
+ include requirements.txt
5
+
6
+ # Include only the core package
7
+ recursive-include src/pdf_section_binding *.py
8
+
9
+ # Exclude all development and utility files
10
+ exclude src/demo.py
11
+ exclude src/section_binding.py
12
+ exclude src/test_section_binding.py
13
+ exclude src/create_test_pdf.py
14
+ exclude src/example_usage.py
15
+ exclude src/add_page_numbers.py
16
+
17
+ # Exclude ad-hoc test files from root
18
+ exclude test_signature_order.py
19
+ exclude quick_test_signature_order.py
20
+ exclude simple_test.py
21
+
22
+ # Exclude publishing and documentation files
23
+ exclude publish.py
24
+ exclude PUBLISHING.md
25
+ exclude QUICK_PUBLISH.md
26
+ exclude PUBLISH_NOW.md
27
+
28
+ # Exclude convenience scripts
29
+ exclude create_test_pdf
30
+ exclude add_page_numbers
31
+ exclude example_usage
32
+
33
+ # Exclude all test files and data
34
+ exclude tests/*
35
+ exclude tests/test-data/*
36
+ recursive-exclude tests *
37
+
38
+ # Exclude development directories
39
+ exclude data/*
40
+ recursive-exclude data *
41
+
42
+ # Exclude build artifacts
43
+ exclude build/*
44
+ recursive-exclude build *
45
+ exclude dist/*
46
+ recursive-exclude dist *
47
+ exclude *.egg-info/*
48
+ recursive-exclude *.egg-info *
49
+
50
+ # Exclude other development files
51
+ exclude .pylintrc
52
+ exclude .coverage
53
+ exclude .gitignore
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdf-section-binding
3
- Version: 1.0.0
3
+ Version: 1.0.1
4
4
  Summary: A CLI tool for reordering PDF pages for section binding (bookbinding)
5
5
  Home-page: https://github.com/sravankumarrekandar/pdf-section-binding
6
6
  Author: Sravan Kumar Rekandar
7
7
  Author-email: Sravan Kumar Rekandar <sravankumarrekandar@example.com>
8
- License: MIT
8
+ License-Expression: MIT
9
9
  Project-URL: Homepage, https://github.com/yourusername/pdf-section-binding
10
10
  Project-URL: Bug Reports, https://github.com/yourusername/pdf-section-binding/issues
11
11
  Project-URL: Source, https://github.com/yourusername/pdf-section-binding
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pdf-section-binding"
7
- version = "1.0.0"
7
+ version = "1.0.1"
8
8
  authors = [
9
9
  {name = "Sravan Kumar Rekandar", email = "sravankumarrekandar@example.com"},
10
10
  ]
11
11
  description = "A CLI tool for reordering PDF pages for section binding (bookbinding)"
12
12
  readme = "README.md"
13
- license = {text = "MIT"}
13
+ license = "MIT"
14
14
  requires-python = ">=3.7"
15
15
  classifiers = [
16
16
  "Development Status :: 4 - Beta",
@@ -1,6 +1,6 @@
1
1
  """Version information for pdf-section-binding."""
2
2
 
3
- __version__ = "1.0.0"
3
+ __version__ = "1.0.1"
4
4
  __author__ = "Sravan Kumar Rekandar"
5
5
  __email__ = "sravankumarrekandar@example.com"
6
6
  __description__ = (
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdf-section-binding
3
- Version: 1.0.0
3
+ Version: 1.0.1
4
4
  Summary: A CLI tool for reordering PDF pages for section binding (bookbinding)
5
5
  Home-page: https://github.com/sravankumarrekandar/pdf-section-binding
6
6
  Author: Sravan Kumar Rekandar
7
7
  Author-email: Sravan Kumar Rekandar <sravankumarrekandar@example.com>
8
- License: MIT
8
+ License-Expression: MIT
9
9
  Project-URL: Homepage, https://github.com/yourusername/pdf-section-binding
10
10
  Project-URL: Bug Reports, https://github.com/yourusername/pdf-section-binding/issues
11
11
  Project-URL: Source, https://github.com/yourusername/pdf-section-binding
@@ -14,6 +14,4 @@ src/pdf_section_binding.egg-info/dependency_links.txt
14
14
  src/pdf_section_binding.egg-info/entry_points.txt
15
15
  src/pdf_section_binding.egg-info/not-zip-safe
16
16
  src/pdf_section_binding.egg-info/requires.txt
17
- src/pdf_section_binding.egg-info/top_level.txt
18
- tests/test_section_binding.py
19
- tests/test-data/.gitkeep
17
+ src/pdf_section_binding.egg-info/top_level.txt
@@ -1,15 +0,0 @@
1
- include README.md
2
- include LICENSE
3
- include pyproject.toml
4
- include requirements.txt
5
- recursive-include src/pdf_section_binding *.py
6
- recursive-include tests *.py
7
- include tests/test-data/.gitkeep
8
-
9
- # Exclude moved utility files from distribution
10
- exclude src/demo.py
11
- exclude src/section_binding.py
12
- exclude src/test_section_binding.py
13
- exclude src/create_test_pdf.py
14
- exclude src/example_usage.py
15
- exclude src/add_page_numbers.py
@@ -1,2 +0,0 @@
1
- # This file ensures the tests/test-data directory is preserved in git
2
- # Test PDFs will be generated here by create_test_pdf.py
@@ -1,296 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Test suite for PDF Section Binding functionality.
4
- """
5
-
6
- import unittest
7
- import tempfile
8
- import os
9
- import sys
10
- import shutil
11
- from pathlib import Path
12
- from unittest.mock import patch, MagicMock
13
-
14
- # Add src directory to path for testing
15
- sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
16
-
17
-
18
- from pdf_section_binding.core import (
19
- calculate_signature_order,
20
- SectionBindingProcessor,
21
- )
22
-
23
-
24
- class TestCalculateSignatureOrder(unittest.TestCase):
25
- """Test the signature order calculation algorithm."""
26
-
27
- def test_4_page_signature_exact_fit(self):
28
- """Test 4-page signature with exact number of pages."""
29
- result = calculate_signature_order(4, 4)
30
- expected = [4, 1, 3, 2]
31
- self.assertEqual(result, expected)
32
-
33
- def test_8_page_signature_exact_fit(self):
34
- """Test 8-page signature with exact number of pages."""
35
- result = calculate_signature_order(8, 8)
36
- expected = [8, 1, 7, 2, 6, 3, 5, 4]
37
- self.assertEqual(result, expected)
38
-
39
- def test_4_page_signature_with_padding(self):
40
- """Test 4-page signature requiring padding."""
41
- # 6 pages should be padded to 8 pages (2 signatures of 4)
42
- result = calculate_signature_order(6, 4)
43
- expected = [4, 1, 3, 2, 5, 6] # Second signature only has pages 5,6
44
- self.assertEqual(result, expected)
45
-
46
- def test_8_page_signature_with_padding(self):
47
- """Test 8-page signature requiring padding."""
48
- # 12 pages should be padded to 16 pages (2 signatures of 8)
49
- result = calculate_signature_order(12, 8)
50
- expected = [
51
- 8,
52
- 1,
53
- 7,
54
- 2,
55
- 6,
56
- 3,
57
- 5,
58
- 4,
59
- 9,
60
- 10,
61
- 11,
62
- 12,
63
- ] # Second signature only has pages 9-12
64
- self.assertEqual(result, expected)
65
-
66
- def test_16_page_signature(self):
67
- """Test 16-page signature."""
68
- result = calculate_signature_order(16, 16)
69
- expected = [16, 1, 15, 2, 14, 3, 13, 4, 12, 5, 11, 6, 10, 7, 9, 8]
70
- self.assertEqual(result, expected)
71
-
72
- def test_40_page_signature(self):
73
- """Test 40-page signature (10 papers)."""
74
- result = calculate_signature_order(40, 40)
75
- expected_length = 40
76
- self.assertEqual(len(result), expected_length)
77
-
78
- # Verify all pages are present
79
- self.assertEqual(set(result), set(range(1, 41)))
80
-
81
- # Check that first and last pages are in expected positions
82
- self.assertEqual(result[0], 40) # Last page first
83
- self.assertEqual(result[1], 1) # First page second
84
- self.assertEqual(result[-2], 21) # Middle pages at end
85
- self.assertEqual(result[-1], 20)
86
-
87
- def test_custom_signature_sizes(self):
88
- """Test various custom signature sizes."""
89
- test_cases = [
90
- (12, 12), # 3 papers
91
- (20, 20), # 5 papers
92
- (24, 24), # 6 papers
93
- (28, 28), # 7 papers
94
- ]
95
-
96
- for total_pages, sig_size in test_cases:
97
- with self.subTest(signature_size=sig_size):
98
- result = calculate_signature_order(total_pages, sig_size)
99
-
100
- # Should have all pages
101
- self.assertEqual(len(result), total_pages)
102
- self.assertEqual(set(result), set(range(1, total_pages + 1)))
103
-
104
- # Should follow the pattern
105
- self.assertEqual(result[0], total_pages) # Last page first
106
- self.assertEqual(result[1], 1) # First page second
107
-
108
- def test_empty_pdf(self):
109
- """Test with 0 pages."""
110
- result = calculate_signature_order(0, 4)
111
- expected = []
112
- self.assertEqual(result, expected)
113
-
114
- def test_single_page(self):
115
- """Test with 1 page."""
116
- result = calculate_signature_order(1, 4)
117
- expected = [1]
118
- self.assertEqual(result, expected)
119
-
120
- def test_multiple_signatures(self):
121
- """Test with multiple 4-page signatures."""
122
- # 10 pages = 2 full signatures (8 pages) + 1 partial signature
123
- # (2 pages padded to 4)
124
- result = calculate_signature_order(10, 4)
125
- expected = [4, 1, 3, 2, 8, 5, 7, 6, 9, 10]
126
- self.assertEqual(result, expected)
127
-
128
- def test_signature_size_validation(self):
129
- """Test that the algorithm works with different valid signature sizes."""
130
- for sig_size in [4, 8, 16, 32]:
131
- result = calculate_signature_order(sig_size, sig_size)
132
- self.assertEqual(len(result), sig_size)
133
- self.assertTrue(all(1 <= page <= sig_size for page in result))
134
-
135
- def test_order_preserves_reading_sequence(self):
136
- """Test that when folded, pages appear in reading order."""
137
- # For a 4-page signature [4,1,3,2], when folded:
138
- # Sheet 1: [4,1] -> when folded becomes pages 1,2 (reading order)
139
- # Sheet 2: [3,2] -> when folded becomes pages 3,4 (reading order)
140
- result = calculate_signature_order(4, 4)
141
- self.assertEqual(result, [4, 1, 3, 2])
142
-
143
- def test_large_document(self):
144
- """Test with a larger document to ensure scalability."""
145
- total_pages = 100
146
- signature_size = 8
147
- result = calculate_signature_order(total_pages, signature_size)
148
-
149
- # Should have exactly 100 pages (no padding needed since 100 % 8 = 4,
150
- # padded to 104)
151
- expected_length = 100 # Only real pages, not padded ones
152
- self.assertEqual(len(result), expected_length)
153
-
154
- # All pages should be within valid range
155
- self.assertTrue(all(1 <= page <= total_pages for page in result))
156
-
157
- # Each page should appear exactly once
158
- self.assertEqual(len(set(result)), len(result))
159
-
160
-
161
- class TestSectionBindingProcessor(unittest.TestCase):
162
- """Test the PDF processing functionality."""
163
-
164
- def setUp(self):
165
- """Set up test fixtures."""
166
- self.temp_dir = tempfile.mkdtemp()
167
- self.input_pdf = os.path.join(self.temp_dir, "input.pdf")
168
- self.output_pdf = os.path.join(self.temp_dir, "output.pdf")
169
- self.processor = SectionBindingProcessor(verbose=False)
170
-
171
- def tearDown(self):
172
- """Clean up test fixtures."""
173
- shutil.rmtree(self.temp_dir, ignore_errors=True)
174
-
175
- @patch("pdf_section_binding.core.PdfReader")
176
- @patch("pdf_section_binding.core.PdfWriter")
177
- @patch("builtins.open")
178
- def test_process_pdf_basic(self, mock_open, mock_pdf_writer, mock_pdf_reader):
179
- """Test basic PDF processing functionality."""
180
- # Mock PDF reader
181
- mock_reader_instance = MagicMock()
182
- mock_reader_instance.pages = [MagicMock() for _ in range(4)] # 4 pages
183
- mock_pdf_reader.return_value = mock_reader_instance
184
-
185
- # Mock PDF writer
186
- mock_writer_instance = MagicMock()
187
- mock_writer_instance.pages = []
188
- mock_pdf_writer.return_value = mock_writer_instance
189
-
190
- # Mock file operations
191
- mock_file = MagicMock()
192
- mock_open.return_value.__enter__.return_value = mock_file
193
-
194
- # Test the function
195
- result = self.processor.process_pdf(self.input_pdf, self.output_pdf, 4)
196
-
197
- # Verify PDF reader was called
198
- mock_pdf_reader.assert_called_once_with(self.input_pdf)
199
-
200
- # Verify PDF writer was created
201
- mock_pdf_writer.assert_called_once()
202
-
203
- # Verify result structure
204
- self.assertEqual(result["input_path"], self.input_pdf)
205
- self.assertEqual(result["output_path"], self.output_pdf)
206
- self.assertEqual(result["total_pages"], 4)
207
- self.assertEqual(result["signature_size"], 4)
208
-
209
- @patch("pdf_section_binding.core.PdfReader")
210
- def test_file_not_found_error(self, mock_pdf_reader):
211
- """Test handling of file not found error."""
212
- mock_pdf_reader.side_effect = FileNotFoundError("File not found")
213
-
214
- with self.assertRaises(FileNotFoundError):
215
- self.processor.process_pdf(self.input_pdf, self.output_pdf, 4)
216
-
217
- @patch("pdf_section_binding.core.PdfReader")
218
- def test_permission_error(self, mock_pdf_reader):
219
- """Test handling of permission error."""
220
- mock_pdf_reader.side_effect = PermissionError("Permission denied")
221
-
222
- with self.assertRaises(PermissionError):
223
- self.processor.process_pdf(self.input_pdf, self.output_pdf, 4)
224
-
225
- @patch("pdf_section_binding.core.PdfReader")
226
- def test_unexpected_error(self, mock_pdf_reader):
227
- """Test handling of unexpected errors."""
228
- mock_pdf_reader.side_effect = Exception("Unexpected error")
229
-
230
- with self.assertRaises(RuntimeError):
231
- self.processor.process_pdf(self.input_pdf, self.output_pdf, 4)
232
-
233
- def test_dry_run_mode(self):
234
- """Test dry run mode."""
235
- with patch("pdf_section_binding.core.PdfReader") as mock_pdf_reader:
236
- # Mock PDF reader
237
- mock_reader_instance = MagicMock()
238
- mock_reader_instance.pages = [MagicMock() for _ in range(8)]
239
- mock_pdf_reader.return_value = mock_reader_instance
240
-
241
- result = self.processor.process_pdf(
242
- self.input_pdf, self.output_pdf, 8, dry_run=True
243
- )
244
-
245
- # Should not create output file in dry run
246
- self.assertTrue(result["dry_run"])
247
- self.assertNotIn("output_pages", result)
248
- self.assertFalse(os.path.exists(self.output_pdf))
249
-
250
-
251
- class TestSignatureOrderLogic(unittest.TestCase):
252
- """Test the mathematical logic of signature ordering."""
253
-
254
- def test_signature_folding_logic(self):
255
- """Test that the signature order follows proper folding logic."""
256
- # For an 8-page signature, the order should be:
257
- # [8,1,7,2,6,3,5,4]
258
- # When printed on 4 sheets and folded:
259
- # Sheet 1: 8(back), 1(front) -> becomes pages 1,2 when folded
260
- # Sheet 2: 7(back), 2(front) -> becomes pages 3,4 when folded
261
- # Sheet 3: 6(back), 3(front) -> becomes pages 5,6 when folded
262
- # Sheet 4: 5(back), 4(front) -> becomes pages 7,8 when folded
263
-
264
- result = calculate_signature_order(8, 8)
265
- expected = [8, 1, 7, 2, 6, 3, 5, 4]
266
- self.assertEqual(result, expected)
267
-
268
- def test_signature_pairing(self):
269
- """Test that pages are properly paired for double-sided printing."""
270
- result = calculate_signature_order(4, 4)
271
- # Result should be [4,1,3,2]
272
- # This creates two sheets: [4,1] and [3,2]
273
- # When folded: sheet 1 becomes pages 1,2 and sheet 2 becomes pages 3,4
274
-
275
- self.assertEqual(result[0], 4) # First page printed
276
- self.assertEqual(result[1], 1) # Second page printed (same sheet, other side)
277
- self.assertEqual(result[2], 3) # Third page printed
278
- self.assertEqual(result[3], 2) # Fourth page printed (same sheet, other side)
279
-
280
- def test_multiple_signature_independence(self):
281
- """Test that multiple signatures are processed independently."""
282
- # 8 pages with 4-page signatures should create 2 independent signatures
283
- result = calculate_signature_order(8, 4)
284
-
285
- # First signature: pages 1-4 -> [4,1,3,2]
286
- first_sig = result[:4]
287
- self.assertEqual(first_sig, [4, 1, 3, 2])
288
-
289
- # Second signature: pages 5-8 -> [8,5,7,6]
290
- second_sig = result[4:]
291
- self.assertEqual(second_sig, [8, 5, 7, 6])
292
-
293
-
294
- if __name__ == "__main__":
295
- # Run tests with verbose output
296
- unittest.main(verbosity=2)