{"id":30336558,"url":"https://github.com/homebackend/multi-file-renamer","last_synced_at":"2025-09-02T17:39:27.867Z","repository":{"id":310247273,"uuid":"1033413130","full_name":"homebackend/multi-file-renamer","owner":"homebackend","description":"Rename multiple files based on trained model or pattern match using Spacy NLP","archived":false,"fork":false,"pushed_at":"2025-08-16T18:12:42.000Z","size":24,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-16T20:23:22.718Z","etag":null,"topics":["jinja2","machine-learning","pyyaml","rename-files","spacy-nlp"],"latest_commit_sha":null,"homepage":"","language":"Python","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/homebackend.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-08-06T19:21:28.000Z","updated_at":"2025-08-16T18:12:45.000Z","dependencies_parsed_at":"2025-08-16T20:23:26.410Z","dependency_job_id":"7ae98d4d-0985-4c29-a7a2-a574c795c3f4","html_url":"https://github.com/homebackend/multi-file-renamer","commit_stats":null,"previous_names":["homebackend/multi-file-renamer"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/homebackend/multi-file-renamer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/homebackend%2Fmulti-file-renamer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/homebackend%2Fmulti-file-renamer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/homebackend%2Fmulti-file-renamer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/homebackend%2Fmulti-file-renamer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/homebackend","download_url":"https://codeload.github.com/homebackend/multi-file-renamer/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/homebackend%2Fmulti-file-renamer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270946414,"owners_count":24672985,"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-18T02:00:08.743Z","response_time":89,"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":["jinja2","machine-learning","pyyaml","rename-files","spacy-nlp"],"created_at":"2025-08-18T05:22:17.307Z","updated_at":"2025-09-02T17:39:27.851Z","avatar_url":"https://github.com/homebackend.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Contributors][contributors-shield]][contributors-url]\n[![Forks][forks-shield]][forks-url]\n[![Stargazers][stars-shield]][stars-url]\n[![Issues][issues-shield]][issues-url]\n[![MIT License][license-shield]][license-url]\n[![LinkedIn][linkedin-shield]][linkedin-url]\n\n# multi-file-renamer\n\n**multi-file-renamer** is used to rename multiple files using [spacy rule based matching](https://spacy.io/usage/rule-based-matching) or using [trained models](https://spacy.io/usage/training).\n\nTo give you a taste, with single command line, following can be achieved:\n\n| Original Name | New Name |\n| :-- | :-- |\n| 8139 Modern Review Volno-39(1926)-inernetdli2015467807.pdf | The_Modern_Review_,Volume_039,(1926).pdf |\n| dli.bengal.10689.11376-THE MODERN REVIEW VOL.122(JULY-DECEMBER)1967-dlibengal1068911376.pdf | The_Modern_Review_,Volume_122,(1967),(July-December).pdf |\n| in.ernet.dli.2015.114056-The Modern Review Vol Lxxi-inernetdli2015114056.pdf | The_Modern_Review_,Volume_071.pdf |\n| Modern Review  1947-03: Vol 1 Iss 1 -modernreviewalcia_194703_1_1.pdf | The_Modern_Review_,Volume_001,No_1,(1947),(March).pdf |\n| Modern Review  Summer 1949: Vol 3 Iss 1 -modernreviewalcia_summer1949_3_1.pdf | The_Modern_Review_,Volume_003,No_1,(1949).pdf |\n| THE MODERN REVIEW VOL.113(JANUARY-JUNE)1963-dlibengal1068916872.pdf | The_Modern_Review_,Volume_113,(1963),(January-June).pdf |\n\nAs can be seen above, **multi-file-renamer** is able to rename files with very different naming structure and conventions into a consistent naming pattern. Depending upon user input **multi-file-renamer** is able to extract relevant information from *original* file name and use it to construct a *new* file name.\n\n\n## Rename files using rule based matching\n\nFor renaming files using rule based matching, user needs to supply spacy [match rules](https://spacy.io/usage/rule-based-matching) that can be used to extract relevant information, *input-output mapping rules* and *template pattern*.\n\n![Name Generation Using Rule Based Matching Flow](doc/NameGenerationUsingRuleBasedMatchingFlow.png \"Name Generation Using Rule Based Matching Flow\")\n\n## Rename files using trained model flow\n\nFor renaming files using trained model, first a trained model is created that will identify named entities. The model is created by generating named entities for a relatively small set of file names. Once model has been created it can be used to predict new file names using user supplied *input-output mapping rules* and *template pattern*.\n\n![Name Generation Using Trained Model Flow](doc/TrainedNameGenerationFlow.png \"Name Generation Using Trained Model Flow\")\n\n# Installation\n## Requirements\n\n**multi-file-renamer** requires *python 3* and a bunch of other dependencies mentioned in `requirements.txt` file.\n\n## Setup\nBefore running **multi-file-renamer** the python environment needs to be setup correctly. Here we are creating a python virtual environment and installing all the dependencies. The instructions are provided for *Linux*, but ideally these should be identical for any *UNIX* like operating system.\n\n### Create virtual environment and install dependencies\n\nThe following Change to the folder/directory containing \n\n```bash\npython -m venv venv\n. venv/bin/activate\npip install -r requirements.txt\n```\n### Activating virtual environment\n\nCreating virtual environment and installing dependencies is one time process. In subsequent runs you just need to activate the virtual environment:\n```bash\n. venv/bin/activate\n```\n\nTo deactivate the virtual environment run the command: `deactivate`.\n\n# Usage\n\nThe way **multi-file-renamer** works is as a first step one generates *new* file names. Next user verifies that names are correct (or even fix them by hand, if required) by checking the *file_names.json* file. And finally rename the files as the last step.\n\n## Generate new file names\n\nFor generating files we have two options:\n\n1. **Using rule based matching**: In this method *new* file names are extracted using predefined static rules specified in file `patterns.yaml`.\n2. **Using trained model**: In this method a model is trained first. For generating training data *rule based matching* is used. Once this training data has generated, next step is to create a model which can be used any number of times to predict the *new* file names.\n\n### Option 1: Using rule based matching\n\nUser needs to create a `patterns.yaml` file similar to one in [samples](samples/patterns.yaml) directory. This file contains *patterns* as per spacy [match rules](https://spacy.io/usage/rule-based-matching) syntax that can be used to extract *named entities*. Additionally it has *input-output mapping rules* that lead to generation of *file name attribute dict*. The *file name attribute dict* together with user supplied template is used to generate *new* files names.\n\nThis is the flow in the daigram form:\n\n```mermaid\ngraph TD;\n    A[\"**Original**\u003cbr\u003e\u003csmall\u003efile name\u003c/small\u003e\"]--\u003eC[\"Named Entities\"];\n    B[\"**patterns** from patterns.yaml file\"]--\u003eC;\n    C--\u003eF[\"file name\u003cbr\u003e*attribute dict*\"];\n    E[\"**input**-**output**\u003cbr\u003emapping rules\u003cbr\u003efrom patterns.yaml\"]--\u003eF;\n    F--\u003eH;\n    G[\"**template**\u003cbr\u003efrom command line\"]--\u003eH[\"**New**\u003cbr\u003e\u003csmall\u003efile name\u003c/small\u003e\"];\n```\n\nMore information about it is available in Configuration section of this page.\n\n#### Extract new file names from old file names\n\nIn this step we create a file (by default `file_names.json`) that contains mapping between *old* file names and *new* file names. User supplies a `patterns.yaml` file that is used to identify named entities and *input-output mapping rules* to help extract the relevant data from named entities. Additionaly user supplies a *template* which is used to generate *new* file names. The template can have [jinja](https://jinja.palletsprojects.com/en/stable/) code allowing for conditional formatting and placeholders values are substituted using *file name attribute dict*.\n\n```bash\npython multi-file-renamer.py \\\n  extract \\\n  -l samples/patterns.yaml \\\n  --excludes in.ernet.dli.2015. \\\n  -s file_names.json \\\n  -m volume \\\n  -t \"The_Review{% if volume is defined %}_,Volume_{{'%03d'|format(volume|int)}}{% endif %}{% if number is defined %},No_{{number}}{% endif %}{% if year is defined %},({{year}}){% endif %}{% if month is defined %},({{month}}){% endif %}.pdf\" \\\n  file1.pdf file2.pdf directory\n```\n\nIn the above command,\n- `-l` specifies the path of `patterns.yaml` file\n- `--excludes` specifies sub strings that are part of original file names but should be ignored as they would interfere with rule matching. In the above example, a file name like `in.ernet.dli.2015.114056-The Modern Review Vol Lxxi-inernetdli2015114056.pdf` would lead to *2015* being identified as year (which is actually just file scan year). So this way, we prevent processing of part of file names.\n- `-s` specifies the file where *original* to *new* file name mapping should be stored.\n- `-m` specifies attribute names which are considered mandatory. That is if they are not found *new* file name is not generated at all.\n- `-t` specifies file name template to be used to generate file name. It supports [jinja](https://jinja.palletsprojects.com/en/stable/) templating syntax.\n- last argument is list of files or directories to be renamed. Note if you provide directories they will be processed recursively.\n\nOnce the above command is executed it will generate a file `file_names.json`.\n\nTo help you understand here is *original* file name =\u003e *file name attribute dict* =\u003e *new* file name table for an example run with [patterns.yaml](samples/patterns.yaml):\n\n| Original Name | File name attribute dict | New Name |\n| :-- | :-- | :-- |\n| 8139 Modern Review Volno-39(1926)-inernetdli2015467807.pdf | `{ \"volume\": \"39\", \"year\": \"1926\" }` | The_Modern_Review_,Volume_039,(1926).pdf |\n| dli.bengal.10689.11376-THE MODERN REVIEW VOL.122(JULY-DECEMBER)1967-dlibengal1068911376.pdf | `{ \"volume\": \"122\", \"year\": \"1967\", \"month\": \"July-December\" }` | The_Modern_Review_,Volume_122,(1967),(July-December).pdf |\n| in.ernet.dli.2015.114056-The Modern Review Vol Lxxi-inernetdli2015114056.pdf | `{ \"volume\": \"71\" }` | The_Modern_Review_,Volume_071.pdf |\n| Modern Review  1947-03: Vol 1 Iss 1 -modernreviewalcia_194703_1_1.pdf | `{ \"volume\": \"1\", \"year\": \"1947\", \"month\": \"March\", \"number\": \"1\" }` | The_Modern_Review_,Volume_001,No_1,(1947),(March).pdf |\n| Modern Review  Summer 1949: Vol 3 Iss 1 -modernreviewalcia_summer1949_3_1.pdf | `{ \"volume\": \"3\", \"year\": \"1949\", \"number\": \"1\" }` | The_Modern_Review_,Volume_003,No_1,(1949).pdf |\n| THE MODERN REVIEW VOL.113(JANUARY-JUNE)1963-dlibengal1068916872.pdf | `{ \"volume\": \"113\", \"year\": \"1963\", \"month\": \"January-June\" }` | The_Modern_Review_,Volume_113,(1963),(January-June).pdf |\n\n### Option 2: Using trained model\n\nThe following diagram explains the entire process:\n\n```mermaid\ngraph TD;\n  subgraph \"Training model\"\n    A[\"**Original**\u003cbr\u003e\u003csmall\u003efile name\u003c/small\u003e\"]--\u003eC[\"Named Entities\"];\n    B[\"**patterns** from patterns.yaml file\"]--\u003eC;\n    C--\u003eD[\"**Training data**\u003cbr\u003esaved to file\"];\n  end\n  subgraph \"Predicting file names\"\n    D--\u003eG[\"file name\u003cbr\u003e*attribute dict*\"];\n    E[\"**unseen**\u003cbr\u003e\u003csmall\u003efile name\u003c/small\u003e\"]--\u003eG;\n    F[\"**input**-**output**\u003cbr\u003emapping rules\u003cbr\u003efrom patterns.yaml\"]--\u003eG;\n    G--\u003eI[\"**New**\u003cbr\u003e\u003csmall\u003efile name\u003c/small\u003e\"];\n    H[\"**template**\u003cbr\u003efrom command line\"]--\u003eI;\n  end\n```\n\n#### Generate Training data\n\n```bash\npython multi-file-renamer.py \\\n  generate \\\n  -l patterns.yaml \\\n  --excludes in.ernet.dli.2015. \\\n  --training-save-path train_data.spacy \\\n  --testing-save-path train_data_dev.spacy \\\n  file1.pdf file2.pdf directory\n```\n\nIn the above command,\n- `--training-save-path` specifies the path where train data is saved\n- `--testing-save-path` specifies the path where test data is saved\n\n#### Generating Model\n\nRun the following command to generate model in the `./output` directory:\n\n```bash\npython -m spacy init config ./config.cfg --lang en --pipeline ner\npython -m spacy train ./config.cfg --output ./output --paths.train ./train_data.spacy --paths.dev ./train_data_dev.spacy\n```\n\n#### Predict New File Names\n\n```bash\npython multi-file-renamer.py \\\n  predict \\\n  --model output/model-best \\\n  -l patterns.yaml \\\n  --excludes in.ernet.dli.2015. \\\n  -m volume \\\n  -p \"The_Modern_Review{% if volume is defined %}_,Volume_{{'%03d'|format(volume|int)}}{% endif %}{% if number is defined %},No_{{number}}{% endif %}{% if year is defined %},({{year}}){% endif %}{% if month is defined %},({{month}}){% endif %}.pdf\"\\\n  file1.pdf file2.pdf directory\n```\n\n`--model` specifies the location of the model. Other options are explained elsewhere.\n\n## Rename original file name to new file name\n\n```bash\npython multi-flile-renamer.py \\\n  rename from \\\n  -l file_names.json \\\n  -s restore_data.json\n```\n\nIn the above command,\n- `-l` specifies the path of file containing *old* to *new* file name mapping.\n- `-s` specifies the path of file that will contain restoration data.\n\nNote, renaming of files takes into account existence of another file with the same name, and will append suffix like `-1` to make it unique.\n\n# Configuration\n\n## Patterns file\n\nPatterns file is a [YAML](https://yaml.org/) file. It has the following structure:\n\n```yaml\n{{match_entity_label}}:\n    input: {{input_rules}}\n    output: {{output_rules}}\n    patterns: {{spacy_match_patterns}} \n```\n\nIn the above, `match_entity_label` is the label to assigned to recognized entity using pattern `spacy_match_patterns` during the NER (Named Entity Recognition) phase. `spacy_match_patterns` is a list of patterns as specified by the [spacy rule based matching](https://spacy.io/usage/rule-based-matching). A sample file is available [here](docs/samples/patterns.yaml).\n\n`input_rules` and `output_rules` specify as to what should be the output produced from a given Named Entity.\n\n## Input Rules\n\n| Input Type | Fields | Value and type | Description |\n| :-- | :-- | :-- | :-- |\n| **single** | **index**  | - an int or\u003cbr\u003e- keyword *start* or\u003cbr\u003e- keyword *end* | Returns text from specific *index* of the matched Span.\u003cbr\u003eEquivalent to `span.doc[index].text`, where\u003cbr\u003e- keyword *start* is same as `span.start`\u003cbr\u003e- keyword *end* is same as `span.end` |\n| **all** | N/A  | N/A  | Returns all text from matched Span\u003cbr\u003eEquivalent to `span.text` |\n| **distinct** | **indexes** | list where each item is either:\u003cbr\u003e- an int or\u003cbr\u003e- keyword *start* or\u003cbr\u003e- keyword *end* | Returns a list of items, each of which is text from specific *index* of the matched Span.\u003cbr\u003eEquivalent to `[span.doc[i].text for i in indexes]`, where\u003cbr\u003e- keyword *start* is same as `span.start`\u003cbr\u003e- keyword *end* is same as `span.end`\u003cbr\u003e- a positive number means an offset from `span.start`\u003cbr\u003e- a negative number means an offset from `span.end` |\n| **multi** | **start**\u003cbr\u003e**end** | Both can be either:\u003cbr\u003e- an int or\u003cbr\u003e- keyword *start* or\u003cbr\u003e- keyword *end* | Returns a list of items, each of which is text from all the indexes between **start** (inclusive) and **end** (exclusive) of the matched Span.\u003cbr\u003eEquivalent to `[span.doc[i].text for i in range(start, end)]`, where\u003cbr\u003e- keyword *start* is same as `span.start`\u003cbr\u003e- keyword *end* is same as `span.end` |\n\n### Example 1\nFor the following output rule:\n\n```yaml\ntype: single\nindex: end\n```\n\nthe table below summarizes the behaviour:\n\n| Input | Output |\n| :-- | :-- |\n| `[ \"volume\", \"46\" ]` | `\"46\"` |\n| `[ \"vol\", \"xvii\" ]` | `\"xvii\"` |\n\n### Example 2\nFor the following output rule:\n\n```yaml\ntype: all\n```\n\nthe table below summarizes the behaviour:\n\n| Input | Output |\n| :-- | :-- |\n| `[ \"volume\", \"46\" ]` | `\"volume 46\"` |\n| `[ \"vol\", \"xvii\" ]` | `\"vol xvii\"` |\n\n### Example 3\n\nFor the following output rule:\n\n```yaml\ntype: distinct\nindexes:\n    - start\n    - end\n```\n\nthe table below summarizes the behaviour:\n\n| Input | Output |\n| :-- | :-- |\n| `[ \"apr\", \"to\", \"nov\" ]` | `[ \"apr\", \"nov\" ]` |\n| `[ \"may\", \"jun\" ]` | `[ \"may\", \"jun\" ]` |\n\n\n### Example 4\n\nFor the following output rule:\n\n```yaml\ntype: multi\nstart: 1\nend: end\n```\n\nthe table below summarizes the behaviour:\n\n| Input | Output |\n| :-- | :-- |\n| `[ \"a\", \"b\", \"c\", \"d\" ]` | `[ \"b\", \"c\", \"d\" ]` |\n| `[ \"a\", \"b\" ]` | `[ \"b\" ]` |\n\n## Output Rules\n\n| Output Type | Fields | Mandatory | Description |\n| :-- | :-- | :-- | :-- |\n| **type** | enum | always | Any one of the values\u003cbr\u003e- single\u003cbr\u003e- multi\u003cbr\u003e**single** means result is a *dict* single key: value pair\u003cbr\u003e**multi** means result is a *dict* of multiple key: value pairs |\n| **index** | str | only if **type** is **single** | This is the key of result dictionary. In other words, this can be referred in file name pattern. |\n| **outputs** | list of output rules | only if **type** is **multi** | Each item is a output rule which is applied against the input. If input is a python *list* or *tuple*, each corresponding output rule is applied against input item. If input is single text, each rule is applied against input. |\n| **handler** | enum | no | This defines the special handler function (already implemented in python code to produce output as per the supplied **args**.\u003cbr\u003eCurrently supported handlers are:\u003cbr\u003e- **convert_roman_nums**\u003cbr\u003e- **date**\u003cbr\u003e- **joiner** |\n| **args** | dict | only in case a **handler** is defined and it requires arguments | |\n\n**Example when type is single**\n\nFor the following output rule:\n```yaml\ntype: single\nindex: year\n```\n\nthe table below summarizes the behaviour:\n\n| Input | Output |\n| :-- | :-- |\n| 2014 | `{ \"year\": \"2014\" }` |\n| 1990 | `{ \"year\": \"1990\" }` |\n\n**Example when type is multi**\n\nFor the following output rule:\n```yaml\n    type: multi\n    outputs:\n      - type: single\n        index: year\n        handler: date\n        args:\n          format: \"%Y\"\n      - type: single\n        index: month\n        handler: date\n        args:\n          format: \"%B\"\n```\n\nthe table below summarizes the behaviour:\n\n| Input | Output |\n| :-- | :-- |\n| 2014-04 | `{ \"year\": \"2014\", \"month\": \"April\" }` |\n| 1990-2 | `{ \"year\": \"1990\", \"month\": \"February\" }` |\n\n### Handlers\n\nHandlers are specialized function that convert given input into a desired output. The handlers receive input text as input and can optionally have additional arguments.\n\n#### convert_roman_nums Handler\n\nThis handler convert input roman numerals, for example *xvi* to its corresponding Indian/Hindu numeric value, viz. *16*. If input is not a roman numeral, it is left unchanged.\n\n**Example**\n\nFor the following output rule:\n```yaml\ntype: single\nindex: volume\nhandler: convert_roman_nums\n```\nthe table below summarizes the behaviour:\n| Input | Output |\n| :-- | :-- |\n| 14 | `{ \"volume\": \"14\" }` |\n| cxiv | `{ \"volume\": \"116\" }` |\n\n#### date Handler\n\nThis handler converts input into format supported by [strftime](https://docs.python.org/3.14/library/datetime.html#datetime.datetime.strftime). This handler takes following arguments:\n\n| Argument | Type | Mandatory | Description |\n| :-- | :-- | :-- | :-- |\n| **format** | str | yes | This parameter specifies the date formatting to use. More specifically, it is same as format parameter of python's [strftime](https://docs.python.org/3.14/library/datetime.html#datetime.datetime.strftime) |\n\n**Example**\n\nFor the following output rule:\n```yaml\ntype: single\nindex: month\nhandler: date\nargs:\n    format: \"%B\"\n```\nthe table below summarizes the behaviour:\n| Input | Output |\n| :-- | :-- |\n| Dec | `{ \"month\": \"December\" }` |\n| december | `{ \"month\": \"December\" }` |\n\n#### joiner Handler\n\nThis handler converts an input which is either python *list* or *tuple* into a single string joining them using supplied separator. This handler takes followin arguments:\n\n| Argument | Type | Mandatory | Description |\n| :-- | :-- | :-- | :-- |\n| **separator** | str | yes | This is the seprator |\n| **outputs** | list of output rules | no | This is list of output rules applied to each item in the input list before joining them together. See example below. |\n| **exclusions** | list of str | no | This is a list of exclusion values. These values will not be considered for output. |\n\nRemember **join** requires input to be either python's *tuple* or *list*.\n\n**Example**\n\nFor the following output rule:\n```yaml\ntype: single\nindex: number\nhandler: joiner\nargs:\n    separator: \"-\"\n    exclusions: [\":\", \"to\"]\n```\nthe table below summarizes the behaviour:\n| Input | Output |\n| :-- | :-- |\n| `[ \"1\", \"to\", \"6\" ]` | `{ \"number\": \"1-6\" }` |\n| `[ \"2\", \"5\" ]` | `{ \"number\": \"2-5\" }` |\n\n\n**Another Example**\n\nFor the following output rule:\n```yaml\ntype: single\nindex: month\nhandler: joiner\nargs:\n    separator: \"-\"\n    outputs:\n    - type: single\n      handler: date\n      args:\n        format: \"%B\"\n    - type: single\n      handler: date\n      args:\n        format: \"%B\"\n```\nthe table below summarizes the behaviour:\n| Input | Output |\n| :-- | :-- |\n| `[ \"jan\", \"apr\" ]` | `{ \"month\": \"January-April\" }` |\n| `[ \"july\", \"october\" ]` | `{ \"month\": \"July-October\" }` |\n\nNote in this case **joiner** handler is calling **date** handler internally.\n\n\u003c!-- MARKDOWN LINKS \u0026 IMAGES --\u003e\n\u003c!-- https://www.markdownguide.org/basic-syntax/#reference-style-links --\u003e\n[contributors-shield]: https://img.shields.io/github/contributors/homebackend/multi-file-renamer.svg?style=for-the-badge\n[contributors-url]: https://github.com/homebackend/multi-file-renamer/graphs/contributors\n[forks-shield]: https://img.shields.io/github/forks/homebackend/multi-file-renamer.svg?style=for-the-badge\n[forks-url]: https://github.com/homebackend/multi-file-renamer/network/members\n[stars-shield]: https://img.shields.io/github/stars/homebackend/multi-file-renamer.svg?style=for-the-badge\n[stars-url]: https://github.com/homebackend/multi-file-renamer/stargazers\n[issues-shield]: https://img.shields.io/github/issues/homebackend/multi-file-renamer.svg?style=for-the-badge\n[issues-url]: https://github.com/homebackend/multi-file-renamer/issues\n[license-shield]: https://img.shields.io/github/license/homebackend/multi-file-renamer.svg?style=for-the-badge\n[license-url]: https://github.com/homebackend/multi-file-renamer/blob/master/LICENSE\n[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge\u0026logo=linkedin\u0026colorB=555\n[linkedin-url]: https://linkedin.com/in/neeraj-jakhar-39686212b\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhomebackend%2Fmulti-file-renamer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhomebackend%2Fmulti-file-renamer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhomebackend%2Fmulti-file-renamer/lists"}