I know I believe in nothing but it is my sweet nothing.:2021年04月05日分

2021/04/05(Mon)

[プログラミング] 車輪の再再再発明してみた

久しぶりにプログラミング言語Cなんぞ書くとほんと忘れておるな、ちょっと前はPowerShellそして最近はPerlに回帰したたので変数宣言しようとすると無意識で$つけてしまうわ。 あとやべーことにprintf(3)の書式すら忘れておった、printf(3)とか自分でワイド文字拡張の実装書いたりもして、かなり念入りに仕様読み込んだはずなんだけどな…

@何を再発明したのよ?

それを実装するのはあなたで100万人目です

な車輪の再再再発明、JSON parser for Cを書いたよ。 まぁ書いたといってもかなり以前にCitrusの内部で使う用に書いて微妙な気分になって放置したやつをひっぱりだして整理しただけなんだがな。

置き場所はいつものBitBucketのリポジトリ、 こちらとなっております。

まだ<sys/rbtree.h>とかN依存コードが若干残ってるので、世界で3人くらいしか動かせないと思う。

@しょーもないもん作って暇なん?

そうっすね、かの文豪であらせられる太宰治も

蝉は、やがて死ぬる午後に気づいた。ああ、私たち、もっと幸せになってよかったのだ。

と書き残しておられる、そんな心境です。

つーか俺だってこんなもん書いてる時間あったらカメラにフィルム詰めて花見に行きたかったですわ(全ギレ)!何が新しい生活様式ですかあああぁ!

書いた当時のモチベを説明すると、Citrusの各ctype/stdenc moduleはlocale/iconvデータのVARIABLEセクションにあるプロパティを読み込んで初期化するのだが、ここの文法がてんでバラバラなのだ。 そんなこんなで自分で書いたmoduleではコード共用すべくcitrus_prop.cというコードを書いたのだが、あまりにもやっつけ仕事でワイに見せるだけで顔真っ赤にしてヤダヤダするくらいの出来栄えなので書き直したかったのだ。

そんでどうせなら流行ってるしJSON形式いいよねというとこでこうなった。

ちなみにNには元々その手のプロパティを扱うライブラリにproplib(3)というものがあるのだけど、libc内でなくlibpropとして独立してる上に少々扱いづらいのだ(個人の感想です、誹謗中傷にあらず)。

@名前は?

えっなにそのAVの導入部みたいな質問…

名前なぁ、libJSONとかにするとjson-cとかの先達らとバッティングして、名前空間汚染野郎など命名されて今や腐臭を放っているインターネッツで誹謗中傷されそうなのでちょい捻って「libJamerSON」とさせていただいた、ワイの尊敬するベーシストのジェームス・ジェマーソンをリスペクト *1

ホラホラこんなクソ記事読んでないで聴け、世紀の名演を。

いやほんと5:05あたりのWhat's Happening Brotherにメドレー繋がるところのベースライン鳥肌ものですわぁ(恍惚)。

まぁ中見てこんなクソコードじゃぁジェマーソンじゃなくて「(検索の)邪魔ー(読んで)損」じゃねーかと言われたら申し訳ないが故人だしまぁいいだろう。

なおワイは命名というプログラマに必須の能力を欠いているので、デスクトップにはtest.cとかunko.plとかxxx/とかあsdfghjk.txtが転がってるのだが、さすがにお外に流すライブラリにそんな名前つけるのはどうかとでつけた名前なので、センスの事はとやかくいうな。

なおAPI名がjson_*でなくjamerson_*になることでタイプ量が4文字増えてめんどいのだけど、Cだしプリプロセッサでよきにはからえ(事故の元)。

@何で今更?

何年も埃かぶったコードをなんで今になってひっぱり出してきたかというと、先日書いた 記事のISBN自動ハイフネーションツールをお遊びがてらCで再実装しようと思った時、RangeMessage.xmlをCの構造体に変換するってーと、例えばC99の不完全配列型を使って

struct range {
	unsigned int min, max, length;
};
struct lengths_map {
	const char *prefix;
	struct range ranges[];
};

みたいな定義にしても、こいつは静的に初期化すること許されざるという制限がある。

struct lengths_map groups_length[] = {
  {
    "978",
    {
      {       0, 5999999,  1 },
      { 6000000, 6499999,  3 },
      ...
      {       0,       0, -1 }, /* end marker */
    },
  },
  {
    "979",
    {
      {       0,  999999,  1 },
      { 1000000, 1299999,  2 },
      ...
      {       0,       0, -1 }, /* end marker */
    }
  },
  {
    NULL
  } /* end marker */
};
とにかく初期化は認めん、ISO-Cのブランドに傷がつくからな…

と怒られるのでな。

なにがISO-Cブランドですかぁああ!! こんなジジババしか使わない言語に権威なんてありませぇええん!

ともあれ、こういうケースでは

struct range {
    unsigned int min, max, length;
};
struct lengths_map {
    const char *prefix;
    struct range *ranges;
};
struct range range_978[] = {
  {       0, 5999999,  1 },
  { 6000000, 6499999,  3 },
  ...
  {       0,       0, -1 }, /* end marker */
};
struct range range_979[] = {
  {       0,  999999,  1 },
  { 1000000, 1299999,  2 },
  ...
  {       0,       0, -1 }, /* end marker */
};
const struct lengths_map groups_length[] = {
  {
    "978",
    range_978
  },
  {
    "979",
    range_979
  }
  ...
  {
    NULL,
    NULL
  } /* end marker */
};

とかすりゃいいだけではあるんだが、ネストが深かったり大量データだったり複雑になってくると正直管理しきれなくなってくるからな(まぁ適当なスクリプト言語で自動生成しちまやいいだけではある)。

それにそもそもの問題として、Cソースに変換してしまうとRangeMessage.xmlが更新されるたびにバイナリを再コンパイルにしなきゃならんのでな。 更新頻度的が頻繁だったりするとわりとおつらいことになる。

なもんでRangeMessage.xmlを読んで動的に構築するのがベターかと思ったのだが、令和の新しい生活様式の時代にまだXMLなのかよ…という気分になるしわざわざlibxml2とか入れたくねえしexpatだと貧弱過ぎるしな *2、そういえば何十年振りだろうDTDが書かれてるXMLみたの読み方完全に忘れてたぞ…

つーことで(その1)XMLはやめてPerlのようなものでサクッとJSONに変換して読み込むことにしたんだけど、json-c入れようとしたらビルドにCMake要求されるのだが、そいつはC++で書かれとるせいでオレオレN6でビルドしたC++11対応のlibstdc++はlibmに不足する関数があるせいで<cmath>使えんのですわ、よってビルドエラーになるのだ *3。 あーlibmのマージ作業もやらんとならねぇな…

つーことで(その2)json-c使うのは諦めた(判断が早い)、そいや昔こんなの書いたねと思い出しひっぱり出してきたというしょーもないお話、どーでもいい話長すぎるわクソが。

@ これ使う意味ある(直球)?

無いねー全く無いねー、json-cとかがGPLなら、GPLを憎むあまりにどこの馬の骨ともしれんワイのクソコードだって2-clause BSDLならforkするぜ!って変人も現れるんだろうけどだいたいこの手のやつMIT Licenseだしな、まぁ要はbitbucketの肥やしになるだけです。

元はlibcに組込む目的なのでなるべくシンプルに余計なもの乗っけないように書いたのだけど、コード量的にもバイナリサイズ的にも他の実装と比べてそう小さいわけでもなく、微妙な気持ちになるよね。

@判った!GitHubにアップしたコードで適正年収診断!ってやつを試す気だね?

やんねーよあんなもん!某メガバンのソースコード流出事件かよ!あんな根拠の無いモン試すの「うわっ…私の年収、低すぎ…?」広告をクリックするタイプの人間だよ!あんなの コンプレックス産業の一形態だよ!

そもそも俺はGitHub使ってねーよ!いつだってマイナーな方を選ぶからBitBucket使ってるよ!逆神だよ!NikonとOLYMPUSだけは俺のせいじゃねーよ!

そういやBitBucketが去年8月でMercurialのサポート止めたけどリリースちゃんと読んでなかったからリポジトリが消されるとは思ってなかったよ! その点Google Codeは消す消すいっていつまでも残ってるよなと思ったけど今確認したら消えてたよ! 介護やらで忙殺されてた時期だったから気づいたらjqPlotってチャートライブラリのをforkしていくつかバグ潰したコードが消えてたよ! まぁ二度と使うことないだろうからいいけど! あんなわりと重大なバグのプルリク送ってもへんじがないしかばね *4なんて知らねーよ! 今だったらChart.js使うよ! 当時まだそっち存在しなかったんだよ!

@使い方は?

ドキュメントなんてあるわけないでしょおおお!私は書いたコードをすぐ放流したいし、すぐ忘れたいんですぅうう!
なのにOSS凶徒陣は何個ドキュメント書いたのかとか継続的インテグレーションがどうだのゴチャゴチャと!
私のコードは花粉症患者のチリ紙なんです!頭の固い先生方に認められなくて結構!!!

なんだとぉ…

@アマミヤ先生あんた正気か!?

ごめんねぇえええええええ!!

@せめて使い勝手がいいとかなんかいいとこないの?

まぁCだからおのずと限界はあるし、APIって好みも人それぞれだから自分の書いたコードの良し悪しとかさっぱりわからん、以下の使用例みて思うところがあれば森の中にわけいってそこに生えてる葦に向かって叫んでください、いつか葦による嘲笑が私のロバの耳にも届くかもしれません。

中身は前述のISBN13の自動ハイフン化ロジックすな。

/*-
 * Copyright (c) 2021 Takehiko NOZAKI,
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

#include "jamerson.h"

static jamerson_value_t value;
static jamerson_object_t lengths_map;

__attribute__((constructor))
static void constructor()
{
	FILE *fp;

	fp = fopen("isbn.json", "r");
	if (fp == NULL)
		abort();
	value = jamerson_load_file(fp);
	if (value == NULL)
		abort();
	lengths_map = jamerson_value_is_object(value);
	if (lengths_map == NULL)
		abort();
}

__attribute__((destructor))
static void destructor()
{
	/* you are paranoia, let's join malloc/free bikesched */
	if (value != NULL)
		jamerson_value_delete(value);
}

static inline int
prefixlen(const char *key, const char *prefix, unsigned int first7, size_t *ret)
{
	jamerson_object_t lengths, range;
	jamerson_array_t ranges;
	size_t siz, i, len;
	jamerson_number_t num;
	unsigned int min, max;

	lengths = jamerson_value_is_object(jamerson_object_get(lengths_map, key));
	if (lengths == NULL)
		return 1;
	ranges = jamerson_value_is_array(jamerson_object_get(lengths, prefix));
	if (ranges == NULL)
		return 1;
	siz = jamerson_array_size(ranges);
	for (i = 0; i < siz; ++i) {
		range = jamerson_value_is_object(jamerson_array_get(ranges, i));
		if (range == NULL)
			return 1;
		num = jamerson_value_is_number(jamerson_object_get(range, "min"));
		if (num == NULL)
			return 1;
		min = (unsigned int)jamerson_number_double_value(num);
		if (min > 9999999)
			return 1;
		num = jamerson_value_is_number(jamerson_object_get(range, "max"));
		if (num == NULL)
			return 1;
		max = (unsigned int)jamerson_number_double_value(num);
		if (max > 9999999)
			return 1;
		if (first7 >= min && first7 <= max) {
			num = jamerson_value_is_number(jamerson_object_get(range, "length"));
			if (num == NULL)
				return 1;
			len = (size_t)jamerson_number_double_value(num);
			if (len > 7)
				return 1;
			*ret = len;
			return 0;
		}
	}
	return 1;
}

static inline unsigned int
first7(const char *s)
{
	char buf[8];
	unsigned long l;

	strlcpy(buf, s, sizeof(buf));
	strlcat(buf, "0000000", sizeof(buf));
	l = strtoul(buf, NULL, 10);
	assert(l <= 9999999);
	return (unsigned int)l;
}

int
isbn13_hyphenate(char *dst, size_t dstsiz, const char *src)
{
	static const char * const prefixes[] = {
	    "groups_length", "publisher_length", NULL
	};
	const char * const *prefix;
	const char *with_hyphen = (const char *)dst;
	size_t srcsiz, len;
	int ret;

	if (dstsiz < 18)
		return 1;
	srcsiz = strlen(src);
	if (srcsiz != 13)
		return 1;

	dstsiz -= 3, srcsiz -= 3;
	memcpy(dst, src, 3);
	dst += 3, src += 3;
	*dst = '\0';

	for (prefix = prefixes; *prefix != NULL; ++prefix) {
		ret = prefixlen(*prefix, with_hyphen, first7(src), &len);
		if (ret)
			return ret;
		if (dstsiz < len + 1 || srcsiz < len)
			return 1;
		dstsiz -= len + 1, srcsiz -= len;
		*dst++ = '-';
		memcpy(dst, src, len);
		dst += len, src += len;
		*dst = '\0';
	}

	if (srcsiz < 2)
		return 1;
	--srcsiz;
	if (dstsiz < srcsiz + 4)
		return 1;
	*dst++ = '-';
	memcpy(dst, src, srcsiz);
	src += srcsiz, dst += srcsiz;
	dst[0] = '-';
	dst[1] = *src;
	dst[2] = '\0';

	return 0;
}

int
main(int argc, char *argv[])
{
	const char *without_hyphen = "9784870999237";
	char with_hyphen[18];

	if (isbn13_hyphenate(with_hyphen, sizeof(with_hyphen), without_hyphen))
		abort();
	printf("%s -> %s\n", without_hyphen, with_hyphen);

	exit(EXIT_SUCCESS);
}

こいつも書き殴っただけのレベルなのでコード整理したら、ISBN10 <-> 13桁変換ロジックなんかと一緒にライブラリとして放流するかもしれない。

読み込むisbn.jsonは以下のperl5のようなもので生成するなどした、Cとかperl5とかそんな加齢臭漂う言語ばかりで大丈夫なんですかね…

#!/usr/pkg/bin/perl

=head1 COPYRIGHT & LICENSE

 Copyright (c) 2021 Takehiko NOZAKI,
 All rights reserved.

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:
 1. Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.
 2. Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.

 THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 SUCH DAMAGE.

=head1 NAME

 isbn_xml2any.pl - parse ISBN RangeMessage.xml and generate code

=head1 SYNOPSIS

 isbn_xml2any.pl [options] [file]

=head1 OPTIONS

=over 8

=item B<--format>

Specify output format, json(default) or c.

=item B<--help>

Print this help message.

=back
 
=cut

use strict;
use warnings;
use Getopt::Long;
use JSON;
use Pod::Simple::Text;
use Pod::Usage;
use XML::XPath;
use XML::XPath::XMLParser;
use Template;

my $meta_name = {
    'Source' => '/ISBNRangeMessage/MessageSource/text()',
    'SerialNumber' => '/ISBNRangeMessage/MessageSerialNumber/text()',
    'Date' => '/ISBNRangeMessage/MessageDate/text()'
};
my $lengths_map_name = {
    'groups_length'    => '/ISBNRangeMessage/EAN.UCCPrefixes/EAN.UCC',
    'publisher_length' => '/ISBNRangeMessage/RegistrationGroups/Group'
};

sub parse_copyright($$)
{
    my ($filename, $copyright) = @_;

    my $ps = new Pod::Simple::Text;
    my $out;
    $ps->output_string(\$out);
    $ps->parse_file($filename);
    my @lines = split(/\n/, $out);
    my $in_copyright = 0;
    foreach my $line (@lines) {
        if ($line =~ m/^COPYRIGHT/) {
            $in_copyright = 1;
        } elsif ($line =~ m/^[[:upper:]]+/) {
            $in_copyright = 0;
        } elsif ($in_copyright) {
            $line =~ s/^[[:space:]]+//;
            push(@{$copyright}, $line);
        }
    }
    shift(@{$copyright});
    pop(@{$copyright});
}

sub parse_lengths_map($$$$)
{
    my ($xpath, $name, $path, $isbn) = @_;

    $isbn->{lengths_map} = {};
    $isbn->{lengths_map}->{$name} = {};
    foreach my $lengths_map ($xpath->find($path)->get_nodelist) {
        my $prefix = $xpath->find('Prefix/text()', $lengths_map)->string_value;
        $isbn->{lengths_map}->{$name}->{$prefix} = [];
        foreach my $rule ($xpath->find('Rules/Rule',
          $lengths_map)->get_nodelist) {
            my $range = $xpath->find('Range/text()', $rule)->string_value;
            die unless ($range =~ m/^([[:digit:]]+)-([[:digit:]]+)$/);
            my ($min, $max) = ($1, $2);
            my $length = $xpath->find('Length/text()', $rule)->string_value;
            die unless ($length =~ m/^([[:digit:]])$/);
            push(@{$isbn->{lengths_map}->{$name}->{$prefix}}, {
                min => int($min),
                max => int($max),
                length => int($length)
            });
        }
    }
}

my $format = 'json';
my $help = 0;
GetOptions(
    'format=s' => \$format,
    'help|?' => \$help
) || die;
pod2usage(1) if $help || $#ARGV > 0;

my $isbn = {};
my $copyright = [];
parse_copyright($0, $copyright);
my $xpath = ($#ARGV < 0)
     ? XML::XPath->new(ioref => \*STDIN)
     : XML::XPath->new(filename => $ARGV[0]);
while (my ($name, $path) = each(%{$meta_name})) {
    $isbn->{meta}->{$name} = $xpath->find($path)->string_value;
}
while (my ($name, $path) = each(%{$lengths_map_name})) {
    parse_lengths_map($xpath, $name, $path, $isbn);
}
my $out;
if ($format eq 'c') {
    my $tt = new Template();
    $tt->process(\*DATA, {
	copyright => $copyright,
	isbn => $isbn
    }, \$out) || die $tt->error;
} elsif ($format eq 'json') {
    my $json = JSON->new->canonical->pretty;
    $out = $json->encode($isbn);
} else {
    die;
}
print $out, "\n";
__END__
__DATA__
[% USE format -%]
[% rfmt = format('%7d') -%]
/*-
[% FOREACH line IN copyright -%]
 * [% line %]
[% END -%]
 */

#ifndef ISBN_TAB_H_
#define ISBN_TAB_H_

/*
 * THIS FILE AUTOMATICALLY GENERATED.  DO NOT EDIT.
 *
 * generated from:
 * RangeMessage.xml(https://www.isbn-international.org/range_file_generation)
[% FOREACH name IN isbn.meta.keys.sort -%]
 * [[% name %]: [% isbn.meta.$name %]]
[% END -%]
 */

struct range {
	unsigned int min, max;
	size_t length;
};

struct lengths_map {
	const char *prefix;
	const struct range *ranges;
};

[% FOREACH name IN isbn.lengths_map.keys.sort -%]
[% lengths = isbn.lengths_map.$name -%]
[% FOREACH prefix IN lengths.keys.sort -%]
[% ranges = lengths.$prefix -%]
static const struct range [% name %]_[% prefix.replace('-', '_') %][] = {
[% FOREACH range IN ranges -%]
	{ [%- rfmt(range.min) -%], [%- rfmt(range.max) -%],  [%- range.length -%] },
[% END -%]
	{ [% rfmt(0) %], [% rfmt(0) %], -1 }
};

[% END -%]
[% END -%]
[% FOREACH name IN isbn.lengths_map.keys.sort -%]
[% lengths = isbn.lengths_map.$name -%]
static const struct lengths_map [% name %][] = {
[% FOREACH prefix IN lengths.keys.sort -%]
	{
		"[% prefix %]",
		[% name %]_[% prefix.replace('-', '_') %]
	},
[% END -%]
	{
		NULL,
		NULL
	}
};

[% END -%]
#endif /* ISBN_TAB_H_ */

@なんでいつも脱線してばかりの読み辛い文章書くの?

自分で読み返しても支離滅裂な文章で埋まってるこのチラシの裏だけど、これは若かりし頃にボブ・ディランの「追憶のハイウェイ61」というアルバムの裏ジャケにある「覚え書き」という散文を読んで感銘を受け、以来まともな文章がひとつも書けなくなったからである。 なので時代が変わればノーベル文学賞もワンチャンあると思っている。

*1:ワイもうベース何年も弾いとらんのでWarwick Streamer君はすっかり錆び切っておられるので、ジェマーソンの死んだベースの弦の音を好みずっと張り替えなかった伝説が再現できそうである。
*2:そいやXML parserもまだ新人の頃に仕事でしかもDelphiで書いたな、まだSOAP/XML-RPCなんて言葉もない時代でDelphiもXML parserなんて無いのにC/S間の通信フォーマットにXML使えって上にいわれ、それなら将軍Delphi用のXML parserをこの屏風から出してくださいといったら、Delphiのプロとやらを高給で連れてきて書かせる流れになったのだけど、出来上がったシロモノは動きませんでした。 ワイの提案したもうexpatをDelphiでラップするでいいじゃんってのもなぜかオープンソースは信用ならんとかいわれ、泣く泣くその残骸を修正といいながらスクラッチで書き直す羽目になったギョーカイ悲惨体験、週に2日だけ来てゴミ書いて単金ピー百万のなんとかさぁあああああん! なおこの経験で文字コードすこしわかるようになった、今ではさっぱりわからん。
*3:まぁCMakeで使ってるのstd::lroundくらいでこいつはlibmには存在してるのでちょっとkludge入れりゃいいだけではあるんだが重厚長大なビルドツール自体がワイ嫌いなのだ、やはりimakeはすべてを解決する。
*4:オリジナルの作者が飽きたんだか心が折れたんだかで無反応になり、しばらくしてから別のGitHub上のforkの方が本家になり、ワイの数個のプルリクは引き継がれなかったもよう。 分散型リポジトリたってプルリクや課題管理みたいなコラボレーション周りは中央集権で意味ねえよな、そりゃGitHub落ちたらみんなSNSでお茶挽きますわね。