{"id":1520,"date":"2013-08-13T18:12:34","date_gmt":"2013-08-14T01:12:34","guid":{"rendered":"https:\/\/dylanmc.ca\/\/-\/?p=1520"},"modified":"2021-12-01T11:26:04","modified_gmt":"2021-12-01T19:26:04","slug":"gnome-break-timer-week-8","status":"publish","type":"post","link":"https:\/\/dylanmc.ca\/\/-\/blog\/2013\/08\/13\/gnome-break-timer-week-8\/","title":{"rendered":"GNOME Break Timer: Week 8"},"content":{"rendered":"\n<p><em>Seriously regretting my boring choice of titles for these blog posts, but it&#8217;s too late to change it now.<\/em><\/p>\n\n\n\n<p>Why, hello there! The last two weeks haven&#8217;t been the brilliantest for my work on GNOME Break Timer &#8211; partly because all my other unrelated projects, which I&#8217;ve been mostly ignoring in favour of Break Timer, have suddenly flared up and demanded attention &#8211; but I still got some nice stuff done. And I passed my statistics course and almost finished a cool charity website. (More on that soon, I hope?).<\/p>\n\n\n\n<!--more-->\n\n\n\n<p>Most importantly, I decided that it&#8217;s time to add some tests. I added a test folder to the build system, and after bracing myself for a nightmare I was really impressed with how easy it was to get going. Further evidence that my fear of tests is entirely irrational.<\/p>\n\n\n\n<p>With all the GNOME project automake stuff set up, I just needed to build a test runner using GLib&#8217;s test framework and list it in the&nbsp;TEST_PROGS variable. <code>make check<\/code> is figured out all on its own. So, that was lovely!<\/p>\n\n\n\n<p>While everyone was off having fun at GUADEC, I fooled around writing unit tests.&nbsp;Of course, I did run into some trickiness: most of my time was spent making the application more testable (cursing Automake some more, putting everything in noinst_LTLIBRARIES), and fixing bugs that I encountered in the process of writing unit tests. This is all for a good cause, though: I am feeling more and more confident that the bus factor for this project can increase beyond 1.<\/p>\n\n\n\n<p>Of course, I didn&#8217;t write unit tests for <em>everything<\/em>. That would be lovely, but it could also take quite a long time. (Quicker now, of course, since all the kinks have been worked out). Instead, I focused on parts of the application that I have broken by accident in the past: monitoring activity, and triggering breaks. The application uses many global things like system time, as well as timers and timeouts. Those can all be rather troublesome to test, unfortunately, but I found my way around them. I created a custom g_get_real_time function that will either return the actual time or a time set by the test suite, so we can rigorously test how certain objects behave as the time changes.<\/p>\n\n\n\n<p>Most of this is quite boring, but I&#8217;m happy with it anyway. I wasn&#8217;t thrilled with glib&#8217;s syntax for writing a test suite &#8211; I&#8217;m used to wrapping these things in objects &#8211; so I borrowed the TestSuite and TestCase classes from libgee&#8217;s test suite, adding some extra twists.<\/p>\n\n\n\n<p>Here is tests.vala, which is used by all of the test runners:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted prettyprint\">\/\/ GLib's TestSuite and TestCase are compact classes, so we wrap them in real GLib.Objects for convenience\n\/\/ This base code is partly borrowed from libgee's test suite, at https:\/\/git.gnome.org\/browse\/libgee\n\npublic abstract class SimpleTestSuite : Object {\n\tprivate GLib.TestSuite g_test_suite;\n\tprivate Adaptor[] adaptors = new Adaptor[0];\n\n\tprivate class Adaptor {\n\t\tprivate SimpleTestSuite test_suite;\n\t\tprivate SimpleTestCase test;\n\n\t\tpublic Adaptor(SimpleTestSuite test_suite, owned SimpleTestCase test) {\n\t\t\tthis.test_suite = test_suite;\n\t\t\tthis.test = (owned)test;\n\t\t}\n\n\t\tprivate string get_short_name() {\n\t\t\tstring base_name = this.test_suite.get_name();\n\t\t\tstring test_full_name = this.test.get_name();\n\t\t\tif (test_full_name.has_prefix(base_name)) {\n\t\t\t\treturn test_full_name.splice(0, base_name.length);\n\t\t\t} else {\n\t\t\t\treturn test_full_name;\n\t\t\t}\n\t\t}\n\n\t\tprivate void setup(void *fixture) {\n\t\t\tthis.test_suite.setup();\n\t\t}\n\n\t\tprivate void run(void *fixture) {\n\t\t\tthis.test.run(this.test_suite);\n\t\t}\n\n\t\tprivate void teardown(void *fixture) {\n\t\t\tthis.test_suite.teardown();\n\t\t}\n\n\t\tpublic GLib.TestCase get_g_test_case() {\n\t\t\treturn new GLib.TestCase(\n\t\t\t\tthis.get_short_name(),\n\t\t\t\t(TestFixtureFunc)this.setup,\n\t\t\t\t(TestFixtureFunc)this.run,\n\t\t\t\t(TestFixtureFunc)this.teardown\n\t\t\t);\n\t\t}\n\t}\n\n\tpublic SimpleTestSuite() {\n\t\tvar name = this.get_name();\n\t\tthis.g_test_suite = new GLib.TestSuite(name);\n\t}\n\n\tpublic void add_to(GLib.TestSuite parent) {\n\t\tparent.add_suite(this.g_test_suite);\n\t}\n\n\tpublic GLib.TestSuite get_g_test_suite() {\n\t\treturn this.g_test_suite;\n\t}\n\n\tpublic string get_name() {\n\t\treturn this.get_type().name();\n\t}\n\n\tpublic void add_test(owned SimpleTestCase test) {\n\t\tvar adaptor = new Adaptor(this, (owned)test);\n\t\tthis.adaptors += adaptor;\n\t\tthis.g_test_suite.add(adaptor.get_g_test_case());\n\t}\n\n\tpublic virtual void setup() {\n\t}\n\n\tpublic virtual void teardown() {\n\t}\n}\n\npublic interface SimpleTestCase : Object {\n\tpublic abstract void run(T context);\n\n\tpublic void add_to(SimpleTestSuite test_suite) {\n\t\ttest_suite.add_test(this);\n\t}\n\n\tpublic string get_name() {\n\t\treturn this.get_type().name();\n\t}\n}\n\nclass TestRunner : Object {\n\tprivate GLib.TestSuite root_suite;\n\n\tprivate File tmp_dir;\n\tconst string SCHEMA_FILE_NAME = \"org.gnome.break-timer.gschema.xml\";\n\n\tpublic TestRunner(ref unowned string[] args, GLib.TestSuite? root_suite = null) {\n\t\tGLib.Test.init(ref args);\n\t\tif (root_suite == null) {\n\t\t\tthis.root_suite = GLib.TestSuite.get_root();\n\t\t} else {\n\t\t\tthis.root_suite = root_suite;\n\t\t}\n\t}\n\n\tpublic void add(SimpleTestSuite suite) {\n\t\tsuite.add_to(this.root_suite);\n\t}\n\n\tpublic virtual void global_setup() {\n\t\ttry {\n\t\t\tvar tmp_path = DirUtils.make_tmp(\"gnome-break-timer-test-XXXXXX\");\n\t\t\ttmp_dir = File.new_for_path(tmp_path);\n\t\t} catch (Error e) {\n\t\t\tGLib.warning(\"Error creating temporary directory for test files: %s\".printf(e.message));\n\t\t}\n\n\t\tstring target_data_path = Path.build_filename(tmp_dir.get_path(), \"share\");\n\t\tstring target_schema_path = Path.build_filename(tmp_dir.get_path(), \"share\", \"glib-2.0\", \"schemas\");\n\n\t\tEnvironment.set_variable(\"GSETTINGS_BACKEND\", \"memory\", true);\n\n\t\tvar original_data_dirs = Environment.get_variable(\"XDG_DATA_DIRS\");\n\t\tEnvironment.set_variable(\"XDG_DATA_DIRS\", \"%s:%s\".printf(target_data_path, original_data_dirs), true);\n\n\t\tFile source_schema_file = File.new_for_path(\n\t\t\tPath.build_filename(get_top_builddir(), \"data\", SCHEMA_FILE_NAME)\n\t\t);\n\n\t\tFile target_schema_dir = File.new_for_path(target_schema_path);\n\t\ttry {\n\t\t\ttarget_schema_dir.make_directory_with_parents();\n\t\t} catch (Error e) {\n\t\t\tGLib.warning(\"Error creating directory for schema files: %s\", e.message);\n\t\t}\n\n\t\tFile target_schema_file = File.new_for_path(\n\t\t\tPath.build_filename(target_schema_dir.get_path(), SCHEMA_FILE_NAME)\n\t\t);\n\n\t\ttry {\n\t\t\tsource_schema_file.copy(target_schema_file, FileCopyFlags.OVERWRITE);\n\t\t} catch (Error e) {\n\t\t\tGLib.warning(\"Error copying schema file: %s\", e.message);\n\t\t}\n\n\t\tint compile_schemas_result = Posix.system(\"glib-compile-schemas %s\".printf(target_schema_path));\n\t\tif (compile_schemas_result != 0) {\n\t\t\tGLib.warning(\"Could not compile schemas in %s\", target_schema_path);\n\t\t}\n\t}\n\n\tpublic virtual void global_teardown() {\n\t\tif (tmp_dir != null) {\n\t\t\tvar tmp_dir_path = tmp_dir.get_path();\n\t\t\tint delete_tmp_result = Posix.system(\"rm -rf %s\".printf(tmp_dir_path));\n\t\t\tif (delete_tmp_result != 0) {\n\t\t\t\tGLib.warning(\"Could not delete temporary files in %s\", tmp_dir_path);\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic int run() {\n\t\tthis.global_setup();\n\t\tGLib.Test.run();\n\t\tthis.global_teardown();\n\t\treturn 0;\n\t}\n\n\tprivate static string get_top_builddir() {\n\t\tvar builddir = Environment.get_variable(\"top_builddir\");\n\t\tif (builddir == null) builddir = \"..\";\n\t\treturn builddir;\n\t}\n}<\/pre>\n\n\n\n<p>And here&#8217;s a really simple test suite and test runner:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted prettyprint\">public class test_Example : SimpleTestSuite {\n\tpublic string? foo;\n\n\tpublic test_Example() {\n\t\tnew test_example_foo_is_bar().add_to(this);\n\t}\n\n\tpublic override void setup() {\n\t\tthis.foo = \"bar\";\n\t}\n}\n\nclass test_example_foo_is_bar : Object, SimpleTestCase&lt;test_Example&gt; {\n\tpublic void run(test_Example context) {\n\t\tassert(context.foo == \"bar\");\n\t}\n}\n\npublic static int main(string[] args) {\n\tvar runner = new TestRunner(ref args);\n\trunner.add(new test_Example());\n\treturn runner.run();\n}<\/pre>\n\n\n\n<p>Of course, you might note that this <em>still<\/em> isn&#8217;t thread-safe since we&#8217;re passing the same test_Example instance as the parameter for all of our SimpleTestCases, and the syntax is slightly unusual, but I&#8217;m quite fond of the extra brevity. One nice bit is this figures out the name of each test based on GObject type information, so we never need to write it explicitly. The test runner ultimately says that a test named &#8220;\/test_Example\/test_example_foo_is_bar&#8221; has passed, and it can deal with all sorts of stuff well away from the test code. It&#8217;s worked well so far, anyway.<\/p>\n\n\n\n<p>So, that&#8217;s about it for the last two weeks. I also submitted an art request for <a href=\"https:\/\/wiki.gnome.org\/GnomeArt\/ArtRequests\/BreakTimerIcon\">some new icons<\/a>, and I&#8217;m going to try following up on that where I can. This is definitely in the &#8220;polish&#8221; phase &#8211; just with a lot yet to be polished.<\/p>\n","protected":false},"excerpt":{"rendered":"<p><em>Seriously regretting my boring choice of titles for these blog posts, but it&#8217;s too late to change it now.<\/em><\/p>\n<p>Why, hello there! The last two weeks haven&#8217;t been the brilliantest for my work on GNOME Break Timer &#8211; partly because all my other unrelated projects, which I&#8217;ve been mostly ignoring in favour of Break Timer, have suddenly flared up and demanded attention &#8211; but I still got some nice stuff done. And I passed my statistics course and almost finished a cool charity website. (More on that soon, I hope?)&hellip;<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"activitypub_content_warning":"","activitypub_content_visibility":"","activitypub_max_image_attachments":3,"activitypub_interaction_policy_quote":"anyone","activitypub_status":"","footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[55],"tags":[],"class_list":["post-1520","post","type-post","status-publish","format-standard","hentry","category-gsoc-2013"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.1.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>GNOME Break Timer: Week 8 - Dylan McCall<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/dylanmc.ca\/\/-\/blog\/2013\/08\/13\/gnome-break-timer-week-8\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"GNOME Break Timer: Week 8 - Dylan McCall\" \/>\n<meta property=\"og:description\" content=\"Seriously regretting my boring choice of titles for these blog posts, but it&#8217;s too late to change it now.  Why, hello there! The last two weeks haven&#8217;t been the brilliantest for my work on GNOME Break Timer &#8211; partly because all my other unrelated projects, which I&#8217;ve been mostly ignoring in favour of Break Timer, have suddenly flared up and demanded attention &#8211; but I still got some nice stuff done. And I passed my statistics course and almost finished a cool charity website. (More on that soon, I hope?)&hellip;\" \/>\n<meta property=\"og:url\" content=\"https:\/\/dylanmc.ca\/\/-\/blog\/2013\/08\/13\/gnome-break-timer-week-8\/\" \/>\n<meta property=\"og:site_name\" content=\"Dylan McCall\" \/>\n<meta property=\"article:published_time\" content=\"2013-08-14T01:12:34+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2021-12-01T19:26:04+00:00\" \/>\n<meta name=\"author\" content=\"Dylan McCall\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Dylan McCall\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"7 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/dylanmc.ca\/\/-\/blog\/2013\/08\/13\/gnome-break-timer-week-8\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/dylanmc.ca\/\/-\/blog\/2013\/08\/13\/gnome-break-timer-week-8\/\"},\"author\":{\"name\":\"Dylan McCall\",\"@id\":\"https:\/\/dylanmc.ca\/\/-\/#\/schema\/person\/c244419a779c5414c768bc53ac5fb2d5\"},\"headline\":\"GNOME Break Timer: Week 8\",\"datePublished\":\"2013-08-14T01:12:34+00:00\",\"dateModified\":\"2021-12-01T19:26:04+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/dylanmc.ca\/\/-\/blog\/2013\/08\/13\/gnome-break-timer-week-8\/\"},\"wordCount\":630,\"articleSection\":[\"GSoC 2013\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/dylanmc.ca\/\/-\/blog\/2013\/08\/13\/gnome-break-timer-week-8\/\",\"url\":\"https:\/\/dylanmc.ca\/\/-\/blog\/2013\/08\/13\/gnome-break-timer-week-8\/\",\"name\":\"GNOME Break Timer: Week 8 - Dylan McCall\",\"isPartOf\":{\"@id\":\"https:\/\/dylanmc.ca\/\/-\/#website\"},\"datePublished\":\"2013-08-14T01:12:34+00:00\",\"dateModified\":\"2021-12-01T19:26:04+00:00\",\"author\":{\"@id\":\"https:\/\/dylanmc.ca\/\/-\/#\/schema\/person\/c244419a779c5414c768bc53ac5fb2d5\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/dylanmc.ca\/\/-\/blog\/2013\/08\/13\/gnome-break-timer-week-8\/\"]}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/dylanmc.ca\/\/-\/#website\",\"url\":\"https:\/\/dylanmc.ca\/\/-\/\",\"name\":\"Dylan McCall\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/dylanmc.ca\/\/-\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/dylanmc.ca\/\/-\/#\/schema\/person\/c244419a779c5414c768bc53ac5fb2d5\",\"name\":\"Dylan McCall\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/dylanmc.ca\/\/-\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/c94ab3a7e6a884542205e0408711cd54bb1fd5f4e90e7a5f621a54656a18a037?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/c94ab3a7e6a884542205e0408711cd54bb1fd5f4e90e7a5f621a54656a18a037?s=96&d=mm&r=g\",\"caption\":\"Dylan McCall\"},\"description\":\"Software developer, tea drinker, GNOME contributor. Occasionally a raving fanatic.\",\"sameAs\":[\"https:\/\/dylanmc.ca\/\/-\"],\"url\":\"https:\/\/dylanmc.ca\/\/-\/blog\/author\/dylan\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"GNOME Break Timer: Week 8 - Dylan McCall","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/dylanmc.ca\/\/-\/blog\/2013\/08\/13\/gnome-break-timer-week-8\/","og_locale":"en_US","og_type":"article","og_title":"GNOME Break Timer: Week 8 - Dylan McCall","og_description":"Seriously regretting my boring choice of titles for these blog posts, but it&#8217;s too late to change it now.  Why, hello there! The last two weeks haven&#8217;t been the brilliantest for my work on GNOME Break Timer &#8211; partly because all my other unrelated projects, which I&#8217;ve been mostly ignoring in favour of Break Timer, have suddenly flared up and demanded attention &#8211; but I still got some nice stuff done. And I passed my statistics course and almost finished a cool charity website. (More on that soon, I hope?)&hellip;","og_url":"https:\/\/dylanmc.ca\/\/-\/blog\/2013\/08\/13\/gnome-break-timer-week-8\/","og_site_name":"Dylan McCall","article_published_time":"2013-08-14T01:12:34+00:00","article_modified_time":"2021-12-01T19:26:04+00:00","author":"Dylan McCall","twitter_misc":{"Written by":"Dylan McCall","Est. reading time":"7 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/dylanmc.ca\/\/-\/blog\/2013\/08\/13\/gnome-break-timer-week-8\/#article","isPartOf":{"@id":"https:\/\/dylanmc.ca\/\/-\/blog\/2013\/08\/13\/gnome-break-timer-week-8\/"},"author":{"name":"Dylan McCall","@id":"https:\/\/dylanmc.ca\/\/-\/#\/schema\/person\/c244419a779c5414c768bc53ac5fb2d5"},"headline":"GNOME Break Timer: Week 8","datePublished":"2013-08-14T01:12:34+00:00","dateModified":"2021-12-01T19:26:04+00:00","mainEntityOfPage":{"@id":"https:\/\/dylanmc.ca\/\/-\/blog\/2013\/08\/13\/gnome-break-timer-week-8\/"},"wordCount":630,"articleSection":["GSoC 2013"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/dylanmc.ca\/\/-\/blog\/2013\/08\/13\/gnome-break-timer-week-8\/","url":"https:\/\/dylanmc.ca\/\/-\/blog\/2013\/08\/13\/gnome-break-timer-week-8\/","name":"GNOME Break Timer: Week 8 - Dylan McCall","isPartOf":{"@id":"https:\/\/dylanmc.ca\/\/-\/#website"},"datePublished":"2013-08-14T01:12:34+00:00","dateModified":"2021-12-01T19:26:04+00:00","author":{"@id":"https:\/\/dylanmc.ca\/\/-\/#\/schema\/person\/c244419a779c5414c768bc53ac5fb2d5"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/dylanmc.ca\/\/-\/blog\/2013\/08\/13\/gnome-break-timer-week-8\/"]}]},{"@type":"WebSite","@id":"https:\/\/dylanmc.ca\/\/-\/#website","url":"https:\/\/dylanmc.ca\/\/-\/","name":"Dylan McCall","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/dylanmc.ca\/\/-\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/dylanmc.ca\/\/-\/#\/schema\/person\/c244419a779c5414c768bc53ac5fb2d5","name":"Dylan McCall","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/dylanmc.ca\/\/-\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/c94ab3a7e6a884542205e0408711cd54bb1fd5f4e90e7a5f621a54656a18a037?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/c94ab3a7e6a884542205e0408711cd54bb1fd5f4e90e7a5f621a54656a18a037?s=96&d=mm&r=g","caption":"Dylan McCall"},"description":"Software developer, tea drinker, GNOME contributor. Occasionally a raving fanatic.","sameAs":["https:\/\/dylanmc.ca\/\/-"],"url":"https:\/\/dylanmc.ca\/\/-\/blog\/author\/dylan\/"}]}},"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/pcXOQX-ow","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/dylanmc.ca\/\/-\/wp-json\/wp\/v2\/posts\/1520","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dylanmc.ca\/\/-\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dylanmc.ca\/\/-\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dylanmc.ca\/\/-\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/dylanmc.ca\/\/-\/wp-json\/wp\/v2\/comments?post=1520"}],"version-history":[{"count":7,"href":"https:\/\/dylanmc.ca\/\/-\/wp-json\/wp\/v2\/posts\/1520\/revisions"}],"predecessor-version":[{"id":11007,"href":"https:\/\/dylanmc.ca\/\/-\/wp-json\/wp\/v2\/posts\/1520\/revisions\/11007"}],"wp:attachment":[{"href":"https:\/\/dylanmc.ca\/\/-\/wp-json\/wp\/v2\/media?parent=1520"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dylanmc.ca\/\/-\/wp-json\/wp\/v2\/categories?post=1520"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dylanmc.ca\/\/-\/wp-json\/wp\/v2\/tags?post=1520"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}