{"id":17218062,"url":"https://github.com/trevoke/sgfparser","last_synced_at":"2025-06-18T12:33:55.612Z","repository":{"id":666337,"uuid":"309475","full_name":"Trevoke/SGFParser","owner":"Trevoke","description":"A Ruby library to parse SGF (Smart Game Format) files, best-known for holding weiqi / go game records","archived":false,"fork":false,"pushed_at":"2024-10-28T18:30:46.000Z","size":665,"stargazers_count":35,"open_issues_count":7,"forks_count":12,"subscribers_count":10,"default_branch":"congruence","last_synced_at":"2025-05-18T01:06:11.621Z","etag":null,"topics":["baduk","ruby","sgf","weiqi"],"latest_commit_sha":null,"homepage":"","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/Trevoke.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG","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}},"created_at":"2009-09-17T04:20:16.000Z","updated_at":"2024-05-19T01:53:50.000Z","dependencies_parsed_at":"2024-05-19T02:37:39.235Z","dependency_job_id":"ecdef4bc-320e-4e5a-85f5-4d716cb8df00","html_url":"https://github.com/Trevoke/SGFParser","commit_stats":{"total_commits":298,"total_committers":12,"mean_commits":"24.833333333333332","dds":0.4530201342281879,"last_synced_commit":"4e397f0b2ea6398f7d675c4f81d519123f111a79"},"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/Trevoke/SGFParser","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Trevoke%2FSGFParser","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Trevoke%2FSGFParser/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Trevoke%2FSGFParser/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Trevoke%2FSGFParser/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Trevoke","download_url":"https://codeload.github.com/Trevoke/SGFParser/tar.gz/refs/heads/congruence","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Trevoke%2FSGFParser/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260552138,"owners_count":23026759,"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":["baduk","ruby","sgf","weiqi"],"created_at":"2024-10-15T03:45:23.311Z","updated_at":"2025-06-18T12:33:50.598Z","avatar_url":"https://github.com/Trevoke.png","language":"Ruby","readme":"# SGFParser\n\n[\u003cimg src=\"https://secure.travis-ci.org/Trevoke/SGFParser.png\" /\u003e](http://travis-ci.org/Trevoke/SGFParser) [![Gitter](https://badges.gitter.im/JoinChat.svg)](https://gitter.im/Trevoke/SGFParser?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge) [![Maintainability](https://api.codeclimate.com/v1/badges/cf8235d1d5ef230a4cf0/maintainability)](https://codeclimate.com/github/Trevoke/SGFParser/maintainability)\n\n\n\n# Intro\nI'm hoping that this is and remains the fastest SGF parser in Ruby. On my desktop, loading the SGF library and parsing Kogo's Joseki dictionary takes a little under six seconds. It's a 3MB file and the average SGF is maybe 10k, so on average it's rather snappy.\n\nAre you using this gem? Is there functionality you wish it had? Is something hard to do? Does the documentation not make sense, and you know how to make it more helpful? Let me know and I'll make it possible, or easier!\n\n# Supported versions\nSGF: FF4 - may support earlier ones as well, but untested.\nRuby: \u003e=2.1\n\n\n# Intro to SGF\nAccording to the standard, An SGF file holds a `Collection` of one or more `Gametree` objects. Each of those is made of a tree of `Node` objects.\n\nIn other words: FILE (1 ↔ 1) Collection (1 ↔ ∞) Gametree (1 ↔ ∞) Node\n\n## Bringing in the code\nSimplicity itself:\n```ruby\nrequire 'sgf'\n```\n\n## Basics of our data structure\n\nIn this implementation, when you parse a file, you get a `Collection` back. This object has a root `Node` used as the top-level node for all gametrees. The children of that node are the root nodes of the actual games.\n\nAssuming a common SGF file with a single game, you could get to the game by doing this:\n\n```ruby\nSGF.parse(filename).gametrees.first # =\u003e \u003cSGF::Game:70180384181460\u003e\n```\n\nIf you have a string, instead, then:\n\n```ruby\nSGF::Parser.new.parse sgf_string\n```\n\n## Basics of properties\n\nSome properties belong on the root node of a game only, such as the identity of the players. For convenience, some human-readable methods are defined on the gametree object itself to reach this information, for instance\n\n```ruby\ngametree.black_player # =\u003e \"tartrate\"\n```\n\nCalling a property that is not defined in the current tree will result in an error. For instance, a property that does not exist in the game of Go:\n\n```ruby\ngametree.black_octisquares # =\u003e SGF::NoIdentityError\n```\n\n## Basics of navigating\n\nSince a game is a tree (each node can be the source of many variations), a convenience method is defined to help you traverse the main branch one node at a time.\n\n```ruby\ngametree.current_node # =\u003e starts as root node, e.g. #\u003cSGF::Node:70180384857820, Has a parent, 1 Children, 16 Properties\u003e\ngametree.next_node    # =\u003e #\u003cSGF::Node:70180384839420, Has a parent, 1 Children, 4 Properties\u003e\ngametree.current_node # =\u003e #\u003cSGF::Node:70180384839420, Has a parent, 1 Children, 4 Properties\u003e\n```\n\nSince it's easy to get lost when you're looking at things one node at a time (or because sometimes you don't want to iterate with an index), we also provide a convenience `depth` method on a given node to tell you how far down the tree you are.\n\nAnd since this is Ruby, all of the objects (`Collection`, `Gametree` and `Node`) provide iteration through `each`. Note that in this example, we are using a gametree, and iteration on a gametree starts from the gametree's root, so the depth is 1. Iteration on a collection starts from the collection's root, and that node's depth would be 0. Iteration on any node starts from that node and goes through all its children.\n\nNOTE: iteration is done as preorder tree traversal. You shouldn't have to care about this, but you might.\n\n```ruby\ngametree.each do |node|\n  puts \"Node at depth #{node.depth} has #{node.properties.count} properties\"\nend\n=begin\nNode at depth 1 has 16 properties\nNode at depth 2 has 4 properties\nNode at depth 3 has 3 properties\nNode at depth 4 has 4 properties\nNode at depth 5 has 3 properties\nNode at depth 6 has 3 properties\nNode at depth 7 has 3 properties\nNode at depth 8 has 4 properties\n... And so on\n=end\n```\n\n## Basics of saving\n\nThere is `SGF::Writer`, which you can use starting from any node. There is also a convenience method on collection:\n\n```ruby\ncollection.save(filename) # =\u003e Shiny new text file\nSGF::Writer.new.stringify_tree_from(node) # =\u003e Shiny string\nSGF::Writer.new.save(node, filename) # =\u003e File with tree starting at node\n```\n\nIf you need a raw SGF version of your data, you can use `to_s`:\n\n```ruby\nnode.to_s\ngametree.to_s\ncollection.to_s\n```\n\n# SGF Parsing warning (À bon entendeur…)\nWARNING: An implementation requirement is to make sure any closing bracket ']' inside a comment is escaped: '\\\\]'. If this is not done, you will be one sad panda! This library will do this for you upon saving, but will most likely die horribly when parsing anything which does not follow this rule.\n\n## Addenda\n\n### Branch name\nThe branch used for publishing the gem is the `congruence` branch. We chose this word because it has strong connotations for proper integration. This branch is congruent. It means all changes brought into this branch are congruent.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftrevoke%2Fsgfparser","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftrevoke%2Fsgfparser","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftrevoke%2Fsgfparser/lists"}