{"id":15405683,"url":"https://github.com/zverok/linkhum","last_synced_at":"2025-04-17T01:53:28.828Z","repository":{"id":34240303,"uuid":"38109992","full_name":"zverok/linkhum","owner":"zverok","description":"URL auto-linker with reasonable and humane behavior","archived":false,"fork":false,"pushed_at":"2020-12-24T17:57:49.000Z","size":32,"stargazers_count":25,"open_issues_count":0,"forks_count":1,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-29T05:51:18.124Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zverok.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-06-26T12:31:51.000Z","updated_at":"2020-12-24T17:57:51.000Z","dependencies_parsed_at":"2022-09-13T18:13:40.254Z","dependency_job_id":null,"html_url":"https://github.com/zverok/linkhum","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zverok%2Flinkhum","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zverok%2Flinkhum/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zverok%2Flinkhum/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zverok%2Flinkhum/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zverok","download_url":"https://codeload.github.com/zverok/linkhum/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248625493,"owners_count":21135513,"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","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":[],"created_at":"2024-10-01T16:18:10.563Z","updated_at":"2025-04-17T01:53:28.811Z","avatar_url":"https://github.com/zverok.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# LinkHum\n\n**LinkHum** (aka \"Links Humana\") is URL auto-linker for user-entered texts.\nIt tries hard to do the most reasonable thing even in complex cases.\n\nIt will be useful for sites with plain-text user input\n\nFeatures:\n* auto-links URL;\n* very accurate detection of punctiations inside and outside of URL;\n* excessive tests set for complex (yet real-life) texts with URLs;\n* customizable behavior.\n\n**NB**: the original algo was written by [@squadette](https://github.com/squadette)\nand the test cases provided by users of [Mokum](https://mokum.place).\nJust gemifying this (on behalf of original author).\n\n## Install\n\n```\n[sudo] gem install linkhum\n```\n\nOr in your Gemfile\n\n```ruby\ngem 'linkhum'\n```\n\nAnd then\n\n```\nbundle install\n```\n\n## Usage\n\nAs simple as:\n\n```ruby\nLinkHum.urlify(\"Please look at http://github.com/zverok/linkhum, it's awesome!\")\n# =\u003e 'Please look at \u003ca href=\"http://github.com/zverok/linkhum\"\u003ehttp://github.com/zverok/linkhum\u003c/a\u003e, it's awesome!'\n```\n\n## Showcase\n\n```ruby\n# Doesn't touch punctuations outside:\nLinkHum.urlify('http://slashdot.org, or http://lwn.net? They say, \"just http://google.com\"')\n# =\u003e \"\u003ca href='http://slashdot.org'\u003ehttp://slashdot.org\u003c/a\u003e, or \u003ca href='http://lwn.net'\u003ehttp://lwn.net\u003c/a\u003e? They say, \\\"just \u003ca href='http://google.com'\u003ehttp://google.com\u003c/a\u003e\\\"\"\n\n# But processes it inside:\nLinkHum.urlify('Watch this: https://www.youtube.com/watch?v=Q9Dv4Hmf_O8')\n# =\u003e \"Watch this: \u003ca href='https://www.youtube.com/watch?v=Q9Dv4Hmf_O8'\u003ehttps://www.youtube.com/watch?v=Q9Dv4Hmf_O8\u003c/a\u003e\"\n\n# Understands parentheses:\nLinkHum.urlify(\"It's a movie: https://en.wikipedia.org/wiki/Hours_(2013_film) It's just parens: (https://www.youtube.com/watch?v=Q9Dv4Hmf_O8)\")\n# =\u003e \"It's a movie: \u003ca href='https://en.wikipedia.org/wiki/Hours_(2013_film)'\u003ehttps://en.wikipedia.org/wiki/Hours_(2013_film)\u003c/a\u003e It's just parens: (\u003ca href='https://www.youtube.com/watch?v=Q9Dv4Hmf_O8'\u003ehttps://www.youtube.com/watch?v=Q9Dv4Hmf_O8\u003c/a\u003e)\"\n\n# URL shortening:\nLinkHum.urlify(\"It's too long: http://www.booking.com/searchresults.ru.html?sid=28c7356c8d0fb6d81de3a45eff97e0fe;dcid=4;bb_asr=2\u0026class_interval=1\u0026csflt=%7B%7D\u0026dest_id=-2167973\u0026dest_type=city\u0026group_adults=2\u0026group_children=0\u0026idf=1\u0026label_click=undef\u0026no_rooms=1\u0026offset=0\u0026review_score_group=empty\u0026score_min=0\u0026si=ai%2Cco%2Cci%2Cre%2Cdi\u0026src=index\u0026ss=Lisbon%2C%20Lisbon%20Region%2C%20Portugal\u0026ss_raw=Lisbon\u0026ssb=empty\")\n# =\u003e \"It's too long: \u003ca href='http://www.booking.com/searchresults.ru.html?sid=28c7356c8d0fb6d81de3a45eff97e0fe;dcid=4;bb_asr=2\u0026class_interval=1\u0026csflt=%7B%7D\u0026dest_id=-2167973\u0026dest_type=city\u0026group_adults=2\u0026group_children=0\u0026idf=1\u0026label_click=undef\u0026no_rooms=1\u0026offset=0\u0026review_score_group=empty\u0026score_min=0\u0026si=ai,co,ci,re,di\u0026src=index\u0026ss=Lisbon,%20Lisbon%20Region,%20Portugal\u0026ss_raw=Lisbon\u0026ssb=empty'\u003ehttp://www.booking.com/searchresults.ru.html?sid=28c7356c8d0f...\u003c/a\u003e\"\n\n# It's customizable:\nLinkHum.urlify(\n  \"It's too long: http://www.booking.com/searchresults.ru.html?sid=28c7356c8d0fb6d81de3a45eff97e0fe;dcid=4;bb_asr=2\u0026class_interval=1\u0026csflt=%7B%7D\u0026dest_id=-2167973\u0026dest_type=city\u0026group_adults=2\u0026group_children=0\u0026idf=1\u0026label_click=undef\u0026no_rooms=1\u0026offset=0\u0026review_score_group=empty\u0026score_min=0\u0026si=ai%2Cco%2Cci%2Cre%2Cdi\u0026src=index\u0026ss=Lisbon%2C%20Lisbon%20Region%2C%20Portugal\u0026ss_raw=Lisbon\u0026ssb=empty\",\n  max_length: 20)\n# =\u003e\n\n# International domains and Non-ASCII paths:\nLinkHum.urlify(\"Domain: http://www.詹姆斯.com/, and path: https://ru.wikipedia.org/wiki/Эффект_Даннинга_—_Крюгера\")\n# =\u003e \"Domain: \u003ca href='http://www.詹姆斯.com/'\u003ehttp://www.詹姆斯.com/\u003c/a\u003e, and path: \u003ca href='https://ru.wikipedia.org/wiki/%D0%AD%D1%84%D1%84%D0%B5%D0%BA%D1%82_%D0%94%D0%B0%D0%BD%D0%BD%D0%B8%D0%BD%D0%B3%D0%B0_%E2%80%94_%D0%9A%D1%80%D1%8E%D0%B3%D0%B5%D1%80%D0%B0'\u003ehttps://ru.wikipedia.org/wiki/Эффект_Даннинга_—_Крюгера\u003c/a\u003e\"\n\n# Look, ma, no XSS!\nLinkHum.urlify('http://example.com/foo?\"\u003ehere.\u003c/a\u003e\u003cscript\u003ewindow.alert(\"wow\");\u003c/script\u003e')\n# =\u003e \"\u003ca href='http://example.com/foo?%22%3Ehere.%3C/a%3E%3Cscript%3Ewindow.alert(%22wow%22);%3C/script%3E'\u003ehttp://example.com/foo?\\\"\u003ehere.\u003c/a\u003e\u003cscript\u003ewindow.alert(\\\"wow\\\")...\u003c/a\u003e\"\n```\n\n## Customization\n\n### On the fly\n\nCustom URL params:\n\n```ruby\nLinkHum.urlify(\"http://oursite.com/posts/12345 has been mentioned at http://cnn.com\"){\n  |uri|\n  uri.host == 'oursite.com' ? {} : {target: '_blank'}\n}\n# =\u003e \"\u003ca href='http://oursite.com/posts/12345'\u003ehttp://oursite.com/posts/12345\u003c/a\u003e has been mentioned at \u003ca href='http://cnn.com' target='_blank'\u003ehttp://cnn.com\u003c/a\u003e\"\n```\n\nProvided block should receive an instance of `Addressable::URI` and\nreturn hash of additional link attributes. You can use it for opening\nforeign links in new tab, or for styling them different (Wikipedia-style),\nor to provide special icons for links to Youtube, Wikipedia and Google...\nUp to you\n\n### Define your own LinkHum\n\n```ruby\nclass MyLinks \u003c LinkHum\n  def link_attrs(uri)\n    {target: '_blank'} unless uri.host == 'oursite.com'\n  end\nend\n\nMyLinks.urlify(\"http://oursite.com/posts/12345 has been mentioned at http://cnn.com\")\n# =\u003e \"\u003ca href='http://oursite.com/posts/12345'\u003ehttp://oursite.com/posts/12345\u003c/a\u003e has been mentioned at \u003ca href='http://cnn.com' target='_blank'\u003ehttp://cnn.com\u003c/a\u003e\"\n```\n\nYou can also define special strings, which should also became URLs on your\nsite:\n\n```ruby\nclass MyLinks \u003c LinkHum\n  special /@(\\S+)\\b/ do |username|\n    \"http://oursite/users/#{username}\"\n  end\nend\n\nMyLinks.urlify(\"Hey, @jude!\")\n# =\u003e \"Hey, \u003ca href='http://oursite/users/jude'\u003e@jude\u003c/a\u003e!\"\n\n# nil or false means no replacements:\nclass MyLinksConditional \u003c LinkHum\n  special /@(\\S+)\\b/ do |username|\n    \"http://oursite/users/#{username}\" if User.where(name: username).exists?\n  end\nend\n\nMyLinksConditional.urlify(\"So, our @dude and @unknownguy walk into a bar...\")\n# =\u003e \"So, our \u003ca href='http://oursite/users/dude'\u003e@dude\u003c/a\u003e and @unknownguy walk into a bar...\"\n```\n\nSome `special` gotchas:\n* in version 0.0.2, you can define any number of `special`s, but it's\n  totally up to you to have non-conflicting, clearly distinguished patterns;\n* it passes to the block values by the same logic as `String#scan` does:\n\n```ruby\nclass AllSymbols \u003c LinkHum\n  special /@\\S+\\b/ do |username|\n    p username\n    nil\n  end\nend\nAllSymbols.urlify('@dude')\n# Receives \"@dude\"\n\nclass SelectedPart \u003c LinkHum\n  special /@(\\S+)\\b/ do |username|\n    p username\n    nil\n  end\nend\nSelectedPart.urlify('@dude')\n# Receives \"dude\"\n\nclass SeveralArgs \u003c LinkHum\n  special(/@(\\S+)_(\\S+)\\b/) do |first, second|\n    p first, second\n    nil\n  end\nend\nSeveralArgs.urlify('@cool_dude')\n# Receives \"cool\", \"dude\"\n```\n\n### \"Parse only\" mode\n\nIf your demands for resulting strings construction is far more complicated\nthan default LinkHum behavior, you can use its `#parse` command to split\nstring into tokens, and process them by yourself. All URL-detection\ngoodness and `special`s still will be with you:\n\n```ruby\nclass MyParser \u003c LinkHum\n  # You don't need rendering blocks for your specials\n  # Second argument is special's name, it is optional\n  special /@(\\S+)\\b/, :username\n  special /\\#(\\S+)\\b/, :tag\nend\n\nMyParser.parse(\"Here is @dude. He is #cute. Is he on http://facebook.com?\")\n# =\u003e [\n#   {type: :text    , content: 'Here is '},\n#   {type: :username, content: '@dude', captures: ['dude']},\n#   {type: :text    , content: '. He is '},\n#   {type: :tag     , content: '#cute', captures: ['cute']},\n#   {type: :text    , content: '. Is he on '},\n#   {type: :url     , content: 'http://facebook.com'},\n#   {type: :text    , content: '?'}\n# ]\n```\n\n## Credits\n\n* [@squadette](https://github.com/squadette) -- author of original code;\n* users of [Mokum](https://mokum.place) -- testing and advicing (and now\n  you can observe LinkHum work online at Mokum);\n* [@zverok](https://github.com/zverok) -- gemifying, documenting and\n  writing specs.\n\n## Contributing\n\nJust usual fork-change-pull request process.\n\n### Development\n\n* Don't forget to use `rspec` after any changes made (and specify them,\n  of course!)\n* It's preferred to use `bundle exec dokaz` to check if README written\n  correctly and `bundle exec dokaz -fshow` to check what exactly code\n  from README will output.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzverok%2Flinkhum","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzverok%2Flinkhum","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzverok%2Flinkhum/lists"}