diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..f054457 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,61 @@ +name: Build and Deploy Documentation + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-22.04 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup OCaml + uses: ocaml/setup-ocaml@v2 + with: + ocaml-compiler: 4.14.x + dune-cache: true + + - name: Install dependencies + run: opam install . --deps-only --with-doc + + - name: Build project + run: opam exec -- dune build + + - name: Build documentation + run: opam exec -- dune build @doc + + - name: Setup Pages + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + uses: actions/configure-pages@v4 + + - name: Upload artifact + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + uses: actions/upload-pages-artifact@v3 + with: + path: '_build/default/_doc/_html' + + deploy: + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-22.04 + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/Makefile b/Makefile index 0da5b8f..32e3d44 100644 --- a/Makefile +++ b/Makefile @@ -5,13 +5,13 @@ DUNE = dune -.PHONY: all install test clean uninstall format install-ocamlformat +.PHONY: all install test clean uninstall format install-ocamlformat doc doc-serve all: $(DUNE) build install: all - $(DUNE) install hello + $(DUNE) install stdnum uninstall: $(DUNE) uninstall @@ -28,10 +28,17 @@ clean: utop: $(DUNE) utop +doc: + $(DUNE) build @doc + +doc-serve: doc + @echo "Starting documentation server at http://localhost:8080" + @echo "Press Ctrl+C to stop" + cd _build/default/_doc/_html && python3 -m http.server 8080 + format: $(DUNE) build --auto-promote @fmt - opam lint --normalise hello.opam > hello.tmp && mv hello.tmp hello.opam - git ls-files '**/*.[ch]' | xargs -n1 indent -nut -i8 + opam lint --normalise stdnum.opam > stdnum.tmp && mv stdnum.tmp stdnum.opam install-ocamlformat: opam install -y ocamlformat=0.26.1 \ No newline at end of file diff --git a/README_DEV.md b/README_DEV.md new file mode 100644 index 0000000..7f69612 --- /dev/null +++ b/README_DEV.md @@ -0,0 +1,80 @@ +# Developer Documentation + +## Building Documentation + +This project uses `odoc` for generating documentation from OCaml interface files. + +### Prerequisites + +Make sure you have `odoc` installed: +```bash +opam install odoc +``` + +### Building Documentation + +To build the documentation: +```bash +make doc +``` + +This will generate HTML documentation in `_build/default/_doc/_html/`. + +### Serving Documentation Locally + +To serve the documentation locally on port 8080: +```bash +make doc-serve +``` + +This will start a Python HTTP server and open the documentation at http://localhost:8080 + +**Note:** You need Python 3 installed for the local server. Press Ctrl+C to stop the server. + +### Documentation Guidelines + +When writing documentation in `.mli` files: + +1. **Module-level documentation**: Use `(** {1 Title} ... *)` at the top +2. **Function documentation**: Use `(** ... *)` before each function +3. **Parameters**: Use `@param name description` +4. **Return values**: Use `@return description` +5. **Exceptions**: Use `@raise Exception description` +6. **Examples**: Use code blocks with `{[ ... ]}` + +#### Example: +```ocaml +(** {1 Number Validation} + + This module validates identification numbers. +*) + +(** [validate number] validates the given number. + + @param number The number to validate + @return The cleaned and validated number + @raise Invalid_format if the number is invalid + + {[validate "123-45-6789" = "123456789"]} *) +val validate : string -> string +``` + +### Documentation Structure + +- `/` - Main library documentation +- `/stdnum/` - Main module +- `/stdnum/Stdnum/` - Core library documentation +- `/stdnum/Tools/` - Utility functions +- `/stdnum/Us/` - US numbers (when implemented) +- `/stdnum/Pl/` - Polish numbers (when implemented) + +### Deployment + +Documentation is automatically deployed to GitHub Pages when pushed to main branch via GitHub Actions. + +## Development Workflow + +1. Write/update `.mli` files with proper documentation +2. Run `make doc` to build locally +3. Run `make doc-serve` to preview at http://localhost:8080 +4. Commit and push - documentation will be auto-deployed \ No newline at end of file diff --git a/dune-project b/dune-project index 060fc8e..6fc7472 100644 --- a/dune-project +++ b/dune-project @@ -13,15 +13,20 @@ ) (license LICENSE) -(documentation https://url/to/documentation) +(documentation https://kupolak.github.io/stdnum/) (package (name stdnum) - (synopsis "Ocaml library to provide functions to handle, parse and validate standard numbers.") + (synopsis "OCaml library to provide functions to handle, parse and validate standard numbers.") (description "The stdnum library in OCaml provides functions for handling, parsing, - and validating standard numbers. It can be used for identifiers like PESEL, NIP, REGON.") - (depends ocaml dune alcotest) + and validating standard numbers. It can be used for identifiers like PESEL, NIP, REGON, + SSN, ITIN, EIN, ATIN and many others from various countries.") + (depends + ocaml + dune + alcotest + (odoc :with-doc)) (tags - (validation, identity-card-validation standards utils check checker))) + (validation identity-card-validation standards utils check checker))) ; See the complete stanza docs at https://dune.readthedocs.io/en/stable/dune-files.html#dune-project diff --git a/lib/ad/dune b/lib/ad/dune index d6f7323..72ad12b 100644 --- a/lib/ad/dune +++ b/lib/ad/dune @@ -1,4 +1,5 @@ (library (name ad) + (public_name stdnum.ad) (modules nrt) (libraries tools)) diff --git a/lib/al/dune b/lib/al/dune index 90f5d36..ff45f36 100644 --- a/lib/al/dune +++ b/lib/al/dune @@ -1,4 +1,5 @@ (library (name al) + (public_name stdnum.al) (modules nipt) (libraries tools)) diff --git a/lib/ar/dune b/lib/ar/dune index 303121c..0f94245 100644 --- a/lib/ar/dune +++ b/lib/ar/dune @@ -1,4 +1,5 @@ (library (name ar) (public_name stdnum.ar) - (libraries stdnum.tools)) + (modules cbu) + (libraries tools)) diff --git a/lib/dune b/lib/dune index a902111..cb4e9f6 100644 --- a/lib/dune +++ b/lib/dune @@ -1,3 +1,4 @@ (library (name stdnum) - (public_name stdnum)) + (public_name stdnum) + (modules_without_implementation stdnum)) diff --git a/lib/hu/dune b/lib/hu/dune index 138dc23..7e4b10f 100644 --- a/lib/hu/dune +++ b/lib/hu/dune @@ -1,4 +1,5 @@ (library (name hu) + (public_name stdnum.hu) (modules anum) (libraries tools)) diff --git a/lib/iso7064/dune b/lib/iso7064/dune index 0f2bd91..f74f1a4 100644 --- a/lib/iso7064/dune +++ b/lib/iso7064/dune @@ -1,4 +1,5 @@ (library (name iso7064) + (public_name stdnum.iso7064) (modules mod_37_2 mod_97_10) (libraries tools)) diff --git a/lib/pl/dune b/lib/pl/dune index 820e46c..3ca9869 100644 --- a/lib/pl/dune +++ b/lib/pl/dune @@ -1,4 +1,5 @@ (library (name pl) + (public_name stdnum.pl) (modules nip pesel regon) (libraries tools)) diff --git a/lib/stdnum.mli b/lib/stdnum.mli new file mode 100644 index 0000000..d261019 --- /dev/null +++ b/lib/stdnum.mli @@ -0,0 +1,124 @@ +(** {1 Standard Number Validation Library} + + OCaml library to provide functions to handle, parse and validate standard numbers. + + This library supports validation of various standard numbers including: + - National identification numbers + - Tax identification numbers + - Bank account numbers + - ISBN/ISSN codes + - And many more + + {2 Organization} + + The library is organized by country/region modules: + - Us - United States numbers (ATIN, etc.) + - Pl - Polish numbers (PESEL, NIP, REGON, etc.) + - Al - Albanian numbers (NIPT) + - Ad - Andorran numbers (NRT) + - Ar - Argentine numbers (CBU) + - Hu - Hungarian numbers (ANUM) + + And utility modules: + - Tools - Common utility functions + - Iso7064 - ISO 7064 checksum algorithms + + {2 Usage Examples} + + {3 Polish Numbers} + {[ + (* Polish NIP (VAT number) validation *) + let nip = "123-456-78-90" in + if Pl.Nip.is_valid nip then + Printf.printf "Valid NIP: %s\n" (Pl.Nip.format nip) + else + Printf.printf "Invalid NIP\n" + + (* Polish PESEL (national ID) validation *) + let pesel = "44051401359" in + try + let validated = Pl.Pesel.validate pesel in + Printf.printf "Valid PESEL: %s\n" (Pl.Pesel.format validated) + with + | Pl.Pesel.Invalid_format -> Printf.printf "Invalid PESEL format\n" + | Pl.Pesel.Invalid_checksum -> Printf.printf "Invalid PESEL checksum\n" + + (* Polish REGON (business number) validation *) + let regon = "123456785" in + assert (Pl.Regon.is_valid regon); + Printf.printf "Formatted REGON: %s\n" (Pl.Regon.format regon) + ]} + + {3 US Numbers} + {[ + (* US ATIN (Adoption Taxpayer ID) validation *) + let atin = "900-93-0000" in + if Us.Atin.is_valid atin then + Printf.printf "Valid ATIN: %s\n" atin + else + Printf.printf "Invalid ATIN\n" + ]} + + {3 Argentine Numbers} + {[ + (* Argentine CBU (bank account) validation *) + let cbu = "0170016212000012345678" in + try + let validated = Ar.Cbu.validate cbu in + Printf.printf "Valid CBU: %s\n" (Ar.Cbu.format validated) + with + | Ar.Cbu.Invalid_format -> Printf.printf "Invalid CBU format\n" + | Ar.Cbu.Invalid_checksum -> Printf.printf "Invalid CBU checksum\n" + ]} + + {3 Albanian Numbers} + {[ + (* Albanian NIPT (tax number) validation *) + let nipt = "J91402501L" in + if Al.Nipt.is_valid nipt then + Printf.printf "Valid NIPT: %s\n" (Al.Nipt.format nipt) + else + Printf.printf "Invalid NIPT\n" + ]} + + {3 Utility Functions} + {[ + (* Using utility functions *) + let number = "123-45-6789" in + let cleaned = Tools.Utils.clean number "-" in + Printf.printf "Cleaned: %s\n" cleaned; (* "123456789" *) + + let is_numeric = Tools.Utils.is_digits cleaned in + Printf.printf "Is numeric: %b\n" is_numeric; (* true *) + ]} + + {3 ISO 7064 Checksums} + {[ + (* Using ISO 7064 checksum algorithms *) + let number = "12345678" in + let checksum = Iso7064.Mod_97_10.checksum number in + Printf.printf "ISO 7064 MOD 97-10 checksum: %d\n" checksum; + + let with_check = Iso7064.Mod_37_2.checksum "ABCDEFGH" in + Printf.printf "ISO 7064 MOD 37-2 checksum: %d\n" with_check + ]} + + {2 Common Patterns} + + Most modules in this library follow a consistent interface: + - [validate] - validates and returns normalized form, raises exception on invalid + - [is_valid] - returns boolean, never raises exceptions + - [format] - formats number to standard presentation + - [compact] - removes formatting characters + - [checksum] - calculates checksum (where applicable) + + {2 Error Handling} + + The library uses OCaml exceptions for error handling: + - [Invalid_format] - number format is incorrect + - [Invalid_length] - number has wrong length + - [Invalid_checksum] - checksum validation failed + + Use [is_valid] functions for boolean checks without exceptions, + or [validate] functions with proper exception handling. +*) \ No newline at end of file diff --git a/lib/tools/utils.mli b/lib/tools/utils.mli index f3cff5a..626be81 100644 --- a/lib/tools/utils.mli +++ b/lib/tools/utils.mli @@ -1,5 +1,24 @@ +(** {1 Utility Functions} + + This module provides common utility functions for string manipulation + and validation used throughout the stdnum library. +*) + +(** [is_digits number] checks if all characters in the string are digits. + + @param number The string to check + @return [true] if all characters are digits (0-9), [false] otherwise + + {[is_digits "123456" = true]} + {[is_digits "123a56" = false]} *) val is_digits : string -> bool -(** [is_digits number] checks if all characters in [number] are digits. *) +(** [clean number deletechars] removes all occurrences of specified characters. + + @param number The input string to clean + @param deletechars String containing characters to remove + @return String with specified characters removed + + {[clean "123-45-6789" "-" = "123456789"]} + {[clean "A B C" " " = "ABC"]} *) val clean : string -> string -> string -(** [clean number deletechars] removes specified characters from [number]. *) diff --git a/lib/us/atin.mli b/lib/us/atin.mli index 9b98e62..951de83 100644 --- a/lib/us/atin.mli +++ b/lib/us/atin.mli @@ -1,22 +1,58 @@ -(* -ATIN (U.S. Adoption Taxpayer Identification Number). +(** {1 Adoption Taxpayer Identification Number (ATIN)} -An Adoption Taxpayer Identification Number (ATIN) is a temporary -nine-digit number issued by the United States IRS for a child for whom the -adopting parents cannot obtain a Social Security Number. + This module provides functions to validate U.S. Adoption Taxpayer Identification Numbers. + + An Adoption Taxpayer Identification Number (ATIN) is a temporary nine-digit number + issued by the United States IRS for a child for whom the adopting parents cannot + obtain a Social Security Number. + + {2 Format} + - 9 digits total with specific pattern: 9XX-93-XXXX + - First digit: must be 9 + - Digits 4-5: must be 93 + - Always formatted with dashes for official use + + {2 Example} + {[ + let valid_atin = "900-93-0000" in + assert (is_valid valid_atin); + assert (validate valid_atin = "900-93-0000") + ]} *) +(** Exception raised when the ATIN format is invalid *) exception Invalid_format +(** Regular expression for matching ATINs with pattern 9XX-93-XXXX + + This regex validates: + - Starts with 9 + - Followed by any two digits + - Then "-93-" + - Ending with any four digits *) val atin_re : Str.regexp -(** Regular expression for matching ATINs *) +(** [validate number] validates and returns the ATIN in standard format. + + @param number The ATIN to validate (must include dashes) + @return The validated ATIN in format 9XX-93-XXXX + @raise Invalid_format if the ATIN is not valid + + {[validate "900-93-0000" = "900-93-0000"]} *) val validate : string -> string -(** Check if the number is a valid ATIN. - This checks the length and formatting if it is present. *) +(** [is_valid number] checks if the ATIN is valid. + + @param number The ATIN to check + @return [true] if valid, [false] otherwise + + {[is_valid "900-93-0000" = true]} *) val is_valid : string -> bool -(** Check if the number is a valid ATIN. *) -val format : string -> string -(** Reformat the number to the standard presentation format. *) +(** [format_number number] formats the ATIN to standard presentation. + + @param number The ATIN to format + @return The formatted ATIN as 9XX-93-XXXX + + {[format_number "900930000" = "900-93-0000"]} *) +val format_number : string -> string