{"id":29938017,"url":"https://github.com/open-technology-foundation/post_slug","last_synced_at":"2025-12-30T21:51:21.006Z","repository":{"id":191072050,"uuid":"683866665","full_name":"Open-Technology-Foundation/post_slug","owner":"Open-Technology-Foundation","description":"The `post_slug` function modules for Python, Bash, Javascript, and PHP, converts any given line of text into a URL- or filename- friendly ASCII slug. Used mainly to generate consistent cross-language slugs for headlines, article titles, book titles, and for URLs and filenames generally. ","archived":false,"fork":false,"pushed_at":"2025-07-31T04:11:54.000Z","size":5871,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-31T06:02:15.702Z","etag":null,"topics":["bash","javascript","modules","php","postslug","python","python3","shell","slugify","slugs"],"latest_commit_sha":null,"homepage":"https://yatti.id/","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Open-Technology-Foundation.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":"AUDIT-EVALUATE.md","citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-08-28T00:05:39.000Z","updated_at":"2025-07-31T04:11:58.000Z","dependencies_parsed_at":"2023-08-28T02:03:57.100Z","dependency_job_id":"6717d845-1c2f-460d-be9d-424978949259","html_url":"https://github.com/Open-Technology-Foundation/post_slug","commit_stats":null,"previous_names":["open-technology-foundation/post_slug"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/Open-Technology-Foundation/post_slug","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Open-Technology-Foundation%2Fpost_slug","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Open-Technology-Foundation%2Fpost_slug/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Open-Technology-Foundation%2Fpost_slug/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Open-Technology-Foundation%2Fpost_slug/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Open-Technology-Foundation","download_url":"https://codeload.github.com/Open-Technology-Foundation/post_slug/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Open-Technology-Foundation%2Fpost_slug/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268470799,"owners_count":24255391,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-02T02:00:12.353Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bash","javascript","modules","php","postslug","python","python3","shell","slugify","slugs"],"created_at":"2025-08-02T23:16:28.972Z","updated_at":"2025-12-30T21:51:20.979Z","avatar_url":"https://github.com/Open-Technology-Foundation.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# post_slug\n\n[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)\n[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)\n[![PHP 8.0+](https://img.shields.io/badge/php-8.0+-777BB4.svg)](https://www.php.net/)\n[![Bash 5.1+](https://img.shields.io/badge/bash-5.1+-4EAA25.svg)](https://www.gnu.org/software/bash/)\n[![Node 12.2+](https://img.shields.io/badge/node-12.2+-339933.svg)](https://nodejs.org/)\n\n\u003e A consistent, cross-language slug generator for creating URL-safe and filename-safe strings from any text input.\n\n## 🎯 Overview\n\n**post_slug** converts any text into clean, readable slugs that are safe for URLs, filenames, and other contexts where only ASCII alphanumeric characters are allowed. With identical implementations in **Python**, **JavaScript**, **PHP**, and **Bash**, it ensures consistent output across your entire stack.\n\n### Key Features\n\n- 🌐 **Cross-language consistency** - Identical output across Python, JavaScript, PHP, and Bash\n- 🛡️ **Security-focused** - Input sanitization with 255-character limit to prevent DoS attacks\n- 🔧 **Flexible configuration** - Customizable separator, case preservation, and length limits\n- 📦 **Zero dependencies** - Uses only built-in language features\n- ⚡ **Fast and lightweight** - Optimized for performance\n- 🧪 **Thoroughly tested** - Comprehensive test suite with cross-language validation\n\n### Quick Example\n\n```python\nfrom post_slug import post_slug\n\n# Convert a complex title to a clean slug\ntitle = \"The Ŝtřãņġę (Inner) Life! of the \\\"Outsider\\\"\"\nslug = post_slug(title)\n# Output: \"the-strange-inner-life-of-the-outsider\"\n```\n\n## 📦 Installation\n\n### Python\n\n```bash\n# Install from PyPI (coming soon)\npip install post-slug\n\n# Or install from source\npython -m pip install .\n```\n\n### JavaScript/Node.js\n\n```bash\n# Install from npm (coming soon)\nnpm install post-slug\n\n# Or use directly\nconst { post_slug } = require('./post_slug.js');\n```\n\n### PHP\n\n```bash\n# Install via Composer (coming soon)\ncomposer require open-technology-foundation/post-slug\n\n# Or include directly\nrequire_once 'post_slug.php';\n```\n\n### Bash\n\n```bash\n# Source the function\nsource post_slug.bash\n\n# Or add to your .bashrc\necho 'source /path/to/post_slug.bash' \u003e\u003e ~/.bashrc\n```\n\n## 🚀 Usage\n\n### Basic Usage\n\nAll implementations share the same API:\n\n```\npost_slug(input_str, [sep_char], [preserve_case], [max_len])\n```\n\n#### Parameters\n\n| Parameter | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `input_str` | string | required | The text to convert into a slug |\n| `sep_char` | string | `'-'` | Character to replace non-alphanumeric characters |\n| `preserve_case` | bool/int | `false`/`0` | Whether to preserve the original case |\n| `max_len` | int | `0` | Maximum length (0 = no limit beyond 255 chars) |\n\n### Language-Specific Examples\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003ePython\u003c/b\u003e\u003c/summary\u003e\n\n```python\nfrom post_slug import post_slug\n\n# Basic usage\nslug = post_slug(\"Hello, World!\")\nprint(slug)  # \"hello-world\"\n\n# With underscore separator\nslug = post_slug(\"Hello, World!\", \"_\")\nprint(slug)  # \"hello_world\"\n\n# Preserve case\nslug = post_slug(\"Hello, World!\", \"-\", True)\nprint(slug)  # \"Hello-World\"\n\n# With max length\nslug = post_slug(\"This is a very long title that needs truncation\", \"-\", False, 20)\nprint(slug)  # \"this-is-a-very-long\"\n\n# HTML entities are replaced\nslug = post_slug(\"Barnes \u0026amp; Noble\")\nprint(slug)  # \"barnes-noble\"\n\n# Special characters and Unicode\nslug = post_slug(\"Über die Universitäts-Philosophie — Schopenhauer, 1851\")\nprint(slug)  # \"uber-die-universitats-philosophie-schopenhauer-1851\"\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eJavaScript\u003c/b\u003e\u003c/summary\u003e\n\n```javascript\nconst { post_slug } = require('./post_slug.js');\n\n// Basic usage\nlet slug = post_slug(\"Hello, World!\");\nconsole.log(slug);  // \"hello-world\"\n\n// With underscore separator\nslug = post_slug(\"Hello, World!\", \"_\");\nconsole.log(slug);  // \"hello_world\"\n\n// Preserve case\nslug = post_slug(\"Hello, World!\", \"-\", true);\nconsole.log(slug);  // \"Hello-World\"\n\n// With max length\nslug = post_slug(\"This is a very long title that needs truncation\", \"-\", false, 20);\nconsole.log(slug);  // \"this-is-a-very-long\"\n\n// Works with modern JavaScript\nconst titles = [\n    \"The Great Gatsby\",\n    \"Pride \u0026 Prejudice\",\n    \"1984\"\n];\nconst slugs = titles.map(title =\u003e post_slug(title));\n// [\"the-great-gatsby\", \"pride-prejudice\", \"1984\"]\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003ePHP\u003c/b\u003e\u003c/summary\u003e\n\n```php\n\u003c?php\nrequire_once 'post_slug.php';\n\n// Basic usage\n$slug = post_slug(\"Hello, World!\");\necho $slug;  // \"hello-world\"\n\n// With underscore separator\n$slug = post_slug(\"Hello, World!\", \"_\");\necho $slug;  // \"hello_world\"\n\n// Preserve case\n$slug = post_slug(\"Hello, World!\", \"-\", true);\necho $slug;  // \"Hello-World\"\n\n// With max length\n$slug = post_slug(\"This is a very long title that needs truncation\", \"-\", false, 20);\necho $slug;  // \"this-is-a-very-long\"\n\n// In a WordPress context\nfunction my_custom_slug($title) {\n    return post_slug($title, '-', false, 200);\n}\nadd_filter('sanitize_title', 'my_custom_slug');\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eBash\u003c/b\u003e\u003c/summary\u003e\n\n```bash\n# Source the function\nsource post_slug.bash\n\n# Basic usage\nslug=$(post_slug \"Hello, World!\")\necho \"$slug\"  # \"hello-world\"\n\n# With underscore separator\nslug=$(post_slug \"Hello, World!\" \"_\")\necho \"$slug\"  # \"hello_world\"\n\n# Preserve case\nslug=$(post_slug \"Hello, World!\" \"-\" 1)\necho \"$slug\"  # \"Hello-World\"\n\n# With max length\nslug=$(post_slug \"This is a very long title that needs truncation\" \"-\" 0 20)\necho \"$slug\"  # \"this-is-a-very-long\"\n\n# Batch processing files\nfor file in *.txt; do\n    new_name=\"$(post_slug \"${file%.txt}\").txt\"\n    mv \"$file\" \"$new_name\"\ndone\n```\n\u003c/details\u003e\n\n## 🛠️ Advanced Features\n\n### Batch File Renaming\n\nThe included `slug-files` utility allows batch renaming of files:\n\n```bash\n# Rename all .txt files in a directory\n./slug-files *.txt\n\n# With custom separator and preserved case\n./slug-files -s _ -p /path/to/files/*\n\n# Dry run (no actual renaming)\n./slug-files -n *.pdf\n```\n\n### Command-Line Usage\n\nCreate convenient command-line aliases:\n\n```bash\n# Add to your shell configuration\nln -s /path/to/post_slug.bash /usr/local/bin/post_slug\nln -s /path/to/slug-files /usr/local/bin/slug-files\n\n# Now use directly\npost_slug \"My Document Title!\"\n# Output: my-document-title\n```\n\n## 🔧 How It Works\n\nThe slug generation process follows these steps:\n\n1. **Input validation** - Truncates input to 255 characters for filesystem safety\n2. **Character normalization** - Applies language-specific transliteration fixes\n3. **HTML entity removal** - Replaces entities like `\u0026amp;` with the separator\n4. **ASCII transliteration** - Converts Unicode to closest ASCII equivalents\n5. **Quote removal** - Strips quotes, apostrophes, and backticks\n6. **Case conversion** - Optionally converts to lowercase\n7. **Character replacement** - Replaces non-alphanumeric chars with separator\n8. **Cleanup** - Removes duplicate/leading/trailing separators\n9. **Length truncation** - Optionally truncates to specified length\n\n### Transliteration Details\n\nDifferent languages use different transliteration methods:\n\n- **Python/JavaScript**: `unicodedata.normalize('NFKD')`\n- **PHP/Bash**: `iconv('UTF-8', 'ASCII//TRANSLIT')`\n\nTo ensure consistency, manual transliteration tables (\"kludges\") handle edge cases:\n\n```python\n# Example kludges\n'€' → 'EUR'  # Euro symbol\n'©' → 'C'    # Copyright\n'®' → 'R'    # Registered trademark\n'™' → '-TM'  # Trademark\n```\n\n## 🧪 Testing\n\n### Running Tests\n\n```bash\n# Run cross-language validation\ncd unittests\n./validate_slug_scripts datasets/headlines.txt\n\n# Test with specific parameters\n./validate_slug_scripts datasets/booktitles.txt 0 '-,_' '0,1'\n\n# Quiet mode (errors only)\n./validate_slug_scripts -q datasets/products.txt\n```\n\n### Test Datasets\n\nThe package includes extensive test datasets:\n\n- `headlines.txt` - News headlines with special characters\n- `booktitles.txt` - Book titles with Unicode and punctuation\n- `products.txt` - Product names with symbols and numbers\n- `edge_cases.txt` - Boundary conditions and special cases\n\n### Unit Tests\n\n```bash\n# Python unit tests\npython -m pytest unittests/test_post_slug.py\n\n# Run all language tests\npython unittests/test_post_slug.py\n```\n\n## ⚠️ Important Notes\n\n### Character Set Limitations\n\nSome character sets cannot be transliterated to ASCII and will result in empty strings:\n\n```python\n# Cyrillic text returns empty string\npost_slug(\"Привет мир\")  # \"\"\n\n# Use with Latin-based alphabets for best results\npost_slug(\"Café résumé\")  # \"cafe-resume\"\n```\n\n### Security Considerations\n\n- Input is automatically limited to 255 characters\n- All implementations include error handling\n- Safe for user-generated content\n- No external command execution (except Bash `iconv`)\n\n### Version Compatibility\n\n| Language | Minimum Version | Tested Version |\n|----------|----------------|----------------|\n| Python | 3.10 | 3.12 |\n| PHP | 8.0 | 8.3 |\n| Bash | 5.1 | 5.2 |\n| Node.js | 12.2 | 20.x |\n\n## 🤝 Contributing\n\nWe welcome contributions! Please follow these guidelines:\n\n1. **Consistency is key** - Changes must be applied to all language implementations\n2. **Test thoroughly** - Run `validate_slug_scripts` to ensure cross-language compatibility\n3. **Update kludge tables** - Submit PRs for new transliteration cases\n4. **Follow conventions** - Match the coding style of each language\n\n### Development Setup\n\n```bash\n# Clone the repository\ngit clone https://github.com/Open-Technology-Foundation/post_slug.git\ncd post_slug\n\n# Run tests\ncd unittests\n./validate_slug_scripts datasets/headlines.txt\n\n# Make changes and verify\n# ... edit files ...\n./validate_slug_scripts -q datasets/booktitles.txt\n```\n\n## 📄 License\n\nThis project is licensed under the GNU General Public License v3.0 or later - see the [LICENSE](LICENSE) file for details.\n\n## 🙏 Acknowledgments\n\n- Inspired by various slug generation libraries across different languages\n- Test datasets compiled from real-world content\n- Special thanks to all contributors\n\n## 📚 See Also\n\n- [CLAUDE.md](CLAUDE.md) - AI assistant guidelines\n- [AUDIT-EVALUATE.md](AUDIT-EVALUATE.md) - Security and code quality audit\n- [PURPOSE-FUNCTIONALITY-USAGE.md](docs/PURPOSE-FUNCTIONALITY-USAGE.md) - Detailed documentation\n\n---\n\n**Repository**: https://github.com/Open-Technology-Foundation/post_slug  \n**Author**: Gary Dean \u003cgarydean@okusi.id\u003e  \n**Version**: 1.0.1","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopen-technology-foundation%2Fpost_slug","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fopen-technology-foundation%2Fpost_slug","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopen-technology-foundation%2Fpost_slug/lists"}