├── .circleci └── config.yml ├── .envrc.sample ├── .gitignore ├── .gitmodules ├── Gemfile ├── Gruntfile.js ├── README.md ├── articles ├── afterword.re ├── best-practice.re ├── book.css ├── catalog.yml ├── config.yml ├── epub_style.scss ├── github-v4.re ├── gqlgen.re ├── graphql-spec.re ├── images │ ├── .gitkeep │ ├── github-v4 │ │ ├── document-explorer.png │ │ ├── document-query-repositoryOwner.png │ │ └── query-viewer.png │ └── introduction │ │ ├── query-and-data.png │ │ └── tbf-chain.png ├── introduction.re ├── layouts │ ├── layout.html.erb │ └── layout.tex.erb ├── locale.yml ├── preface.re ├── prh.yml ├── sty │ ├── bxglyphwiki.sty │ ├── jumoline.sty │ ├── pdftexcmds.sty │ ├── reviewmacro.sty │ ├── samplemacro.sty │ ├── tatsumacro.sty │ ├── techbooster-doujin.sty │ └── ulem.sty ├── style-web.scss ├── style.scss └── system-operation.re ├── build-in-docker.sh ├── code ├── best-practice │ ├── .gitignore │ ├── complexity-sample │ │ ├── complexity.go │ │ ├── generated.go │ │ ├── gqlgen.yml │ │ ├── models_gen.go │ │ ├── resolver.go │ │ ├── schema.graphql │ │ └── server │ │ │ └── server.go │ └── custom-scalars │ │ ├── custom_scalars.go │ │ ├── generated.go │ │ ├── go.sum │ │ ├── gqlgen.yml │ │ ├── models_gen.go │ │ ├── resolver.go │ │ ├── schema.graphql │ │ └── server │ │ └── server.go ├── github-v4 │ ├── go-client │ │ └── main.go │ └── text │ │ ├── query-viewer.graphql │ │ └── query-viewer.graphql.result ├── go.mod └── go.sum ├── package-lock.json ├── package.json ├── redpen-conf-ja.xml └── setup.sh /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: vvakame/review:2.4 6 | steps: 7 | - checkout 8 | - restore_cache: 9 | keys: 10 | - npm-cache-{{ checksum "package-lock.json" }} 11 | - run: 12 | name: Setup 13 | command: npm install 14 | - save_cache: 15 | key: npm-cache-{{ checksum "package-lock.json" }} 16 | paths: 17 | - ./node_modules 18 | - run: 19 | name: Test 20 | command: npm test 21 | - run: 22 | name: Build PDF 23 | command: npm run pdf 24 | - store_artifacts: 25 | path: ./articles/TBF4-TechBooster.pdf 26 | destination: TBF4-TechBooster.pdf 27 | -------------------------------------------------------------------------------- /.envrc.sample: -------------------------------------------------------------------------------- 1 | export GITHUB_TOKEN= 2 | export GO111MODULE=on 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | articles/*.pdf 3 | articles/*-pdf/ 4 | 5 | .envrc 6 | 7 | # Graphvizとか使うと生成されるやつ 8 | articles/images/html/ 9 | articles/images/latex/ 10 | 11 | # 他の環境で作ったlockがあるとCIがコケる 12 | Gemfile.lock 13 | 14 | .DS_Store 15 | Thumbs.db 16 | 17 | articles/images/cover/ 18 | articles/*.html 19 | scripts/*.js 20 | distribute/ 21 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "prh-rules"] 2 | path = prh-rules 3 | url = https://github.com/prh/rules.git 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # A sample Gemfile 2 | source "https://rubygems.org" 3 | 4 | gem 'review', '2.5.0' 5 | gem 'review-peg', '0.2.2' 6 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | let fs = require("fs"); 4 | let yaml = require("js-yaml"); 5 | 6 | const articles = "articles"; 7 | const bookConfig = yaml.safeLoad(fs.readFileSync(`${articles}/config.yml`, "utf8")); 8 | 9 | const reviewPrefix = process.env["REVIEW_PREFIX"] || "bundle exec "; 10 | const reviewPostfix = process.env["REVIEW_POSTFIX"] || ""; // REVIEW_POSTFIX="-peg" npm run pdf とかするとPEGでビルドできるよ 11 | const reviewPreproc = `${reviewPrefix}review-preproc${reviewPostfix}`; 12 | const reviewCompile = `${reviewPrefix}review-compile${reviewPostfix}`; 13 | const reviewPdfMaker = `${reviewPrefix}review-pdfmaker${reviewPostfix}`; 14 | const reviewEpubMaker = `${reviewPrefix}review-epubmaker${reviewPostfix}`; 15 | 16 | module.exports = grunt => { 17 | grunt.initConfig({ 18 | clean: { 19 | review: { 20 | src: [ 21 | `${articles}/${bookConfig.bookname}-*/`, // pdf, epub temp dir 22 | `${articles}/*.pdf`, 23 | `${articles}/*.epub`, 24 | `${articles}/*.html`, 25 | `${articles}/*.md`, 26 | `${articles}/*.xml`, 27 | `${articles}/*.txt` 28 | ] 29 | } 30 | }, 31 | shell: { 32 | preprocess: { 33 | options: { 34 | execOptions: { 35 | cwd: articles, 36 | } 37 | }, 38 | command: `${reviewPreproc} -r --tabwidth=2 *.re` 39 | }, 40 | compile2text: { 41 | options: { 42 | execOptions: { 43 | cwd: articles, 44 | } 45 | }, 46 | command: `${reviewCompile} --target=text` 47 | }, 48 | compile2markdown: { 49 | options: { 50 | execOptions: { 51 | cwd: articles, 52 | } 53 | }, 54 | command: `${reviewCompile} --target=markdown` 55 | }, 56 | compile2html: { 57 | options: { 58 | execOptions: { 59 | cwd: articles, 60 | } 61 | }, 62 | command: `${reviewCompile} --target=html --stylesheet=style.css --chapterlink` 63 | }, 64 | compile2latex: { 65 | options: { 66 | execOptions: { 67 | cwd: articles, 68 | } 69 | }, 70 | command: `${reviewCompile} --target=latex --footnotetext` 71 | }, 72 | compile2idgxml: { 73 | options: { 74 | execOptions: { 75 | cwd: articles, 76 | } 77 | }, 78 | command: `${reviewCompile} --target=idgxml` 79 | }, 80 | compile2pdf: { 81 | options: { 82 | execOptions: { 83 | cwd: articles, 84 | } 85 | }, 86 | command: `${reviewPdfMaker} config.yml` 87 | }, 88 | compile2epub: { 89 | options: { 90 | execOptions: { 91 | cwd: articles, 92 | } 93 | }, 94 | command: `${reviewEpubMaker} config.yml` 95 | } 96 | } 97 | }); 98 | 99 | function generateTask(target) { 100 | return ["clean", "shell:preprocess", `shell:compile2${target}`]; 101 | } 102 | 103 | grunt.registerTask( 104 | "default", 105 | "原稿をコンパイルしてPDFファイルにする", 106 | "pdf"); 107 | 108 | grunt.registerTask( 109 | "text", 110 | "原稿をコンパイルしてTextファイルにする", 111 | generateTask("text")); 112 | 113 | grunt.registerTask( 114 | "markdown", 115 | "原稿をコンパイルしてMarkdownファイルにする", 116 | generateTask("markdown")); 117 | 118 | grunt.registerTask( 119 | "html", 120 | "原稿をコンパイルしてHTMLファイルにする", 121 | generateTask("html")); 122 | 123 | grunt.registerTask( 124 | "idgxml", 125 | "原稿をコンパイルしてInDesign用XMLファイルにする", 126 | generateTask("idgxml")); 127 | 128 | grunt.registerTask( 129 | "pdf", 130 | "原稿をコンパイルしてpdfファイルにする", 131 | generateTask("pdf")); 132 | 133 | grunt.registerTask( 134 | "epub", 135 | "原稿をコンパイルしてepubファイルにする", 136 | generateTask("epub")); 137 | 138 | require('load-grunt-tasks')(grunt); 139 | }; 140 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # graphql-with-go-book 2 | 3 | ### PDF出力する 4 | 5 | rbenvやnodebrewを利用してRubyやNode.jsの準備をしている前提です。 6 | もしそうではない場合、適宜sudoを補うこと。 7 | Node.jsは10.x系以上が必須です。 8 | 9 | ``` 10 | $ gem install bundler 11 | $ git clone git@github.com:vvakame/graphql-with-go-book.git 12 | $ cd graphql-with-go-book 13 | $ npm install 14 | $ npm run pdf 15 | ``` 16 | 17 | ### Dockerを使う 18 | 19 | TeXの環境構築が困難な場合、一式セットアップ済みの[dockerイメージ](https://registry.hub.docker.com/u/vvakame/review/)を用意してあるので使ってください。 20 | Dockerがうまく動くようになっている場合、以下のコマンドで細かい準備なしにビルドを行うことができます。 21 | 22 | ``` 23 | $ ./build-in-docker.sh 24 | ``` 25 | -------------------------------------------------------------------------------- /articles/afterword.re: -------------------------------------------------------------------------------- 1 | = よもやま話 2 | 3 | 最後にどうでもいい与太話をば… 4 | 5 | 自分で色々とやってみて、GraphQLサーバはDatabase+Directiveによる権限制御+MutationをうまくこなせればOKらしいという肌感が得られてきました。 6 | これについて考えると、モノリシックな設計で理想的なものを妄想します。 7 | すると、Firebase Realtime DBかFirestoreがGraphQLをサポートし、Directiveの制御やMutation周りをCloud Functionsで実装する…というのがお手軽そうです。 8 | 何も考えずにSubscriptionも使えるだろうし…最高やな! 9 | とはいえ、Google先生は今のところGraphQLにまったく関心がない@{rejoiner}ように見えます。 10 | …かと思いきや、先日行われていたサーベイ@{firestore-survey}でFirestoreにほしいものある?の一覧にGraphQLがあったのは興奮しました。 11 | 12 | //footnote[rejoiner][例外→@{https://github.com/google/rejoiner}] 13 | //footnote[firestore-survey][10/5で終了 @{https://groups.google.com/forum/#!topic/google-cloud-firestore-discuss/8Ub8v1a8o-M}] 14 | 15 | この本で触れられていない点として、ツール類の話をしていません。 16 | ので、一瞬ここで話をします。 17 | その1!graphql-schema-linter@{graphql-schema-linter}! 18 | その2!prettier@{prettier}! 19 | 以上!終了! 20 | 必要に応じて探しただけなのでこれら以外に有名なものがあるのかはよく知らないです。 21 | 他にいいツールがあったら筆者までお知らせください! 22 | 23 | //footnote[graphql-schema-linter][@{http://npmjs.com/package/graphql-schema-linter}] 24 | //footnote[prettier][@{https://www.npmjs.com/package/prettier}] 25 | 26 | はい。 27 | あとマイクロサービス上でのGraphQL運用についても今後経験値を積んでいきたいですね。 28 | OpenCensusとかロギングの話も全然してないし…。 29 | 30 | 最後にエディタの話をしたかったんですが特に推したいものがなかったのであった…。 31 | エディタ上で動くPlayground的なもの欲しい気がするんだけどね。 32 | Visual Studio CodeかGoLandのプラグインや拡張でよいものがあったら筆者までお伝えください。 33 | 34 | それじゃあみんな、GraphQL、やっていこうな! 35 | -------------------------------------------------------------------------------- /articles/best-practice.re: -------------------------------------------------------------------------------- 1 | = ベストプラクティス・バッドプラクティス 2 | 3 | はい。筆者がgqlgenとその周辺をやっていった知見をここに吐き出していきます。 4 | この章がこの本のメインであり、ここまでの章はここにたどり着くまでの撒き餌みたいなものです。 5 | 6 | == N+1問題 7 | 8 | N+1問題が何かから説明していきましょう。 9 | たとえば、技術書典の1イベントあたりサークルが500弱あり、そこに頒布物が複数あるわけです。 10 | これを愚直にDBから取得しようとすると、1+500弱回のクエリが必要になります。 11 | これがN+1問題です。 12 | 1+N問題の間違いでは…? 13 | 14 | GraphQLでも、何も考えずにコードを書いていくとこの問題が発生します 15 | GraphQLサーバはResolverの集まりですので、イベントを1件resolveし、それに紐づく500弱のサークルをresolveし、さらにそれぞれの頒布物をresolveします。 16 | これはN+1問題そのものです。 17 | 18 | これを解決するために、複数のResolverでのクエリを一定数バッファリングし、バッチ化する必要があります。 19 | Goでこれを行う一般的な方法はありませんが、gqlgenの作者であるvektahさんがdataloaden@{dataloaden}というライブラリを作っています。 20 | これは、FacebookがNode.js向けに作っているDataLoader@{dataloader}からインスパイアされたものです。 21 | dataloadenでは、特定の種類のデータへのリクエストを一定時間スリープしてバッファリングします。 22 | その一定期間の間に溜まったリクエストをバッチ化し効率化を図ります。 23 | 24 | //footnote[dataloaden][@{https://github.com/vektah/dataloaden}] 25 | //footnote[dataloader][@{https://github.com/facebook/dataloader}] 26 | 27 | この方法が効率的かといわれるとなかなか難しいのですが、妥当な落とし所ではあるでしょう。 28 | gqlgenでは厄介な(素晴らしい)ことに、Resolverは並行に動作します。 29 | さすがGo言語だぜ! 30 | ここで各Resolverがなるべく並行して動作し続けられるようにすることを考えます。 31 | バッチ処理を実際に行うタイミングをResolverの兄弟Resolverがすべて終わったタイミングとすると、もっとも遅いResolverの処理速度に律速されてしまいます。 32 | であれば、gqlgen+dataloadenのように処理をバッファする期間を一定時間とするのはある程度妥当であると思われます。 33 | 34 | #@# prh:disable 35 | さて、筆者はgqlgenを技術書典Webサイトで動かそうとしていますが、dataloadenを使っていません。 36 | 技術書典WebサイトはAppEngine/Go上で動いています。 37 | もちろんDBはDatastoreです。 38 | 39 | Datastoreを操作するためのライブラリとして、go.mercari.io/datastore@{mercari/datastore}を使っています。 40 | このライブラリにはDatastoreへの各種リクエストをバッチ処理化するためのAPIがデフォルトで準備@{batch-example}されています。 41 | そして、既存のREST APIも内部ではこのバッチ処理を使っていたため、何も考えずともN+1問題への対策ができていました。 42 | 43 | //footnote[mercari/datastore][@{https://godoc.org/go.mercari.io/datastore}] 44 | //footnote[batch-example][@{https://godoc.org/go.mercari.io/datastore#example-package--Batch}] 45 | 46 | 面白かったトラブルとして、go.mercari.io/datastoreのバッチ処理は何十並列という並行環境に晒されたことがなかったため、バグが潜んでいました。 47 | この問題については後ほどの節でもう少し詳しく言及します。 48 | 49 | //comment{ 50 | 具体的に、バッチ処理をExecした時、他のgoroutineがExecした直後だとタスクが空であるため、自分が積んだタスクの終了を待たずに制御が返ってきてしまうという問題がありました。 51 | これにより、並列処理させると一部のstructがゼロ値で返ってくることがある、という気がつくまではまったく意味のわからないバグに悩まされました。 52 | 読者諸氏も、各Resolverが並行に処理されるということを念頭に正しいコードを書くように心がけましょう。 53 | //} 54 | 55 | == REST APIからの移行 56 | 57 | すでにREST APIのシステムを抱えているは多いと思います。 58 | 技術書典Webも、ucon@{ucon}というWebフレームワークでSwaggerとセットのREST APIを作成しています。 59 | これに対して、GraphQLのAPIも別途作っていきました。 60 | 61 | //footnote[ucon][@{https://github.com/favclip/ucon/}] 62 | 63 | この節で言いたいことは、"GraphQLはQuery LanguageであってRemote Procedure Callではない"ということです。 64 | Mutationについては普通にRPCだと思うのでREST APIとさほどの違いは無いと言っていいでしょう。 65 | 66 | === エラーメッセージの粒度 67 | 68 | RPCとQuery Languageでは、返すべきエラーメッセージの粒度が異なります。 69 | たとえば、APIを叩いて、素っ気ない"bad request"の文字が返ってきたとします。 70 | RPCであれば、ドキュメントを調べたり、ソースコードを読んで理由を確かめる作業を耐えることができました。 71 | しかし、不思議なことにQuery Languageではこの粒度のエラーメッセージが返ってくると耐え難い苦痛を感じます。 72 | 73 | これについてよくよく考えると、RPCというのは関数であり、個々の関数の仕様を把握し正しく呼び出すのは呼び出し側の責務であるという意識があるためでしょう。 74 | 対して、Query Languageの場合、syntax自体が正しければエラー無く返ってくるべきと我々は考えます。 75 | SQLクエリをRDBに投げてみたら"bad request"が返ってきたら、普通ブチギレてそのDBMSを窓から放り投げるでしょう。 76 | 77 | よって、GraphQLではエラーを返す場合、そのクエリのどこが悪かったのか、明快に分かるようなエラーメッセージを返すべきです。 78 | 79 | === Query Languageとエラー 80 | 81 | 1つ前の項目で述べたように、REST APIはRPCでGraphQLはQuery Languageです。 82 | ということで、GraphQLでは可能な限りvalidなsyntaxを書いたらちゃんとエラーではないレスポンスが返ってくる、という状況を作る必要があります。 83 | 84 | 実際に、GitHubのGraphQLエンドポイントを叩いてみて、rate limitやnode limit以外のエラーが返ってきたことは無いように思います。 85 | そして、rate limitやnode limitの場合も、詳細なエラーメッセージが返ってきます。 86 | かしこい! 87 | 我々もこれに倣っていかねばなりません。 88 | 89 | REST APIからの移行の場合、特定の検索条件の組み合わせはエラーにしなければならない、というパターンもあるでしょう。 90 | その場合、詳細なエラーメッセージを出力し、GraphiQLの外に出ずにデバッグできるようにします。 91 | GraphiQLの外のドキュメントを見に行かねばならない場合、GraphQLの嬉しさがかなり減じてしまいます。 92 | クエリを書く人が自走しやすくするためには、1にエラーメッセージ、2にSchemaに書かれたドキュメントの充実を心がけるとよいでしょう。 93 | 94 | 技術書典Webでも、エラーハンドラ周りを改修し、詳細なエラーメッセージを簡単に出力できるように整備しました。 95 | 96 | エラーメッセージの他に、Directiveを使って制御を行う手法があります。 97 | これについてはこの後の項目で触れます。 98 | 99 | == Relay Global Object Identification 100 | 101 | #@# prh:disable 102 | めんどくさ仕様No.1の話です。 103 | 仕様についてざっくりと説明すると…。 104 | 105 | 1. @{interface Node { id: ID! \}}インタフェースを作ります 106 | 2. @{node(id: ID!): Node}をQueryの定義に生やします 107 | 3. IDはシステム全体でデータを一意に定められるようにします 108 | 109 | というものです。 110 | そうすると、データの更新やらなにやらで便宜をはかってあげますよ、というものらしいです。 111 | 仕様自体は@{https://facebook.github.io/relay/graphql/objectidentification.htm}にあります。 112 | 113 | さて、こいつのめんどくさいところはご利益がわかりにくいところです。 114 | 筆者はまだこの仕様をサポートすることによりどういうメリットが得られるのか体感できていません。 115 | そのくせ、実装がすこぶるめんどくさいので、やる気がでません@{global-id-pros}。 116 | 117 | //footnote[global-id-pros][IDが一意になればMutationやSubscriptionの返り値にしたがって部分木を自動更新… とかはまぁ分かるんだけど…] 118 | 119 | IDでデータを一意に定められるようにする、というのが特にめんどくさいポイントです。 120 | Userテーブルがあり、CircleExhibitInfoテーブルがあり、CircleTicketテーブルがあり、コレ以外にもさまざまなテーブルがあります。 121 | これらに含まれるすべてのデータを一意に識別できなければいけないわけで、いわゆるAUTO INCREMENT的なPKの運用をしているとそれ単体ではテーブルが識別できなくて辛い気持ちになります。 122 | 123 | GitHubのGraphQLエンドポイントはすでにこの仕様をサポートします。 124 | 試しに自分のIDを調べてみると、@{MDQ6VXNlcjEyNTMzMg==}というIDが返ってきます。 125 | これをBase64 decodeしてみると@{04:User125332}が得られます。 126 | さらに、databaseIdというフィールドが別途存在していて、125332という値が入っています。 127 | これはつまり、v4 API経由のUserテーブルの125332というPKのデータ、という意味なのでしょう。 128 | 129 | #@# prh:disable 130 | 多くのREST APIベースのシステムではテーブルのPKをIDとして扱い、URLにも組み込んでしまっているでしょう。 131 | たとえば、@{https://techbookfest.org/event/tbf05/circle/33000001}のような感じです。 132 | このURLからIDを切り出し、@{GET /api/circle/33000001}を叩いたりするわけです。 133 | 同じことをこの仕様のもとにやろうとした場合、@{/event/tbf05/circle/Q2lyY2xlRXhoaWJpdEluZm86MzMwMDAwMDEK}のようなURL設計にするか、Global IDをアプリ側で合成できる必要があります。 134 | そのため、Global IDの書式をどのようにするか、というのはなかなか意思決定が必要でしょう。 135 | 今の所、技術書典では@{CircleExhibitInfo:33000001}を@{base64.RawURLEncoding}で処理する方式を採用する予定です。 136 | 137 | しかし、メ社内でMTC2018@{mtc2018}を作る際、@{CircleExhibitInfo:33000001}のままでよくね? 138 | humanreadableで何か問題ある?という議論も行われ、現時点で最適解は定まっていません。 139 | 筆者的にも別にbase64にする必要はないのでは…?みたいな顔をし始めたところです。 140 | 141 | #@# prh:disable 142 | //footnote[mtc2018][@{https://github.com/mercari/mtc2018-web}] 143 | 144 | WebページとしてのURL設計については、RESTfulな構造から脱出するのは難しく、また脱出したところで益がありません。 145 | よって、RESTfulなURL設計とGraphQLでのIDの設計についてどう折り合いをつけるか、というのは必ず気にするべきポイントです。 146 | なんなら、Relay Global Object Identificationを捨てる、という選択も視野に入るかもしれません。 147 | 148 | やるとわかりますが@{interface Node { id: ID! \}}の実装もクソめんどくさいです。 149 | gqlgen以外だとなんか楽な仕掛けがあったりするんだろうか…。 150 | 151 | == GitHub v4 APIから設計を読み取る 152 | 153 | ここまでの項目でもさんざん参考にしているので、みなさんもやっていきましょう。 154 | GitHubのAPIはv3までは普通にREST APIで、サーバ側もRailsですので実装に苦闘の痕を伺うことができて面白いです。 155 | 歴史的経緯を想像し、あまり見習うべきではないパーツはしっかり避け、よい工夫を汲み取って学習していきましょう。 156 | 157 | == GraphQLのセキュリティ 158 | 159 | セキュリティ面についても少し触れます。 160 | GraphQLでも通常のREST API的な開発と同様の内容は踏襲していきます。 161 | 入力された引数を信じない、プレースホルダ的なものをしっかり使う、などはGraphQLでも普通に気をつけましょう。 162 | 163 | GraphQL固有の問題はというと、"柔軟なクエリを書けてしまう"問題があります。 164 | たとえば、過去のイベントを5件取得し、それにぶら下がるサークルを100件取得し、そのサークルが属するイベントを取得し、それにぶらさがる(略)、のような乱暴なクエリを書いたりしていくとあっという間にコストが爆発します。 165 | 166 | GraphQLの場合、ツリーの節が増えるごとに掛け算式に取得するデータが増えていきます。 167 | このようなDDoSを意図していなくてもDDoSになりがちなクエリに対して、どのように実行する前に防御するか、というところが肝心です。 168 | 169 | GitHubが採用している方式として、Node limitとRate limitがあります。 170 | それぞれざっくり、指定したクエリが得られる最大のツリーのNodeの数、そして1時間あたりに使えるコスト(≒Node limitsの和)の数です。 171 | 172 | //footnote[github-limits][@{https://developer.github.com/v4/guides/resource-limitations/}] 173 | 174 | これを踏まえて見ると、GitHubのクエリ系は必ず件数のlimitを指定させる設計になっていて、クエリ実行前に最大のNode数が計算できるよう工夫されています。 175 | これは我々も見習うべき知見で、データが何個あるか知らんけど無限に返したろwwwみたいな雑な死に方を回避できます。 176 | 177 | gqlgenにも同様のコスト計算のための仕組みがあります。 178 | それがcomplexityです@{gqlgen-complexity}。 179 | complexity自体はgraphql-rubyにある@{graphql-ruby-complexity}考え方です。 180 | 181 | //footnote[gqlgen-complexity][@{https://gqlgen.com/reference/complexity/}] 182 | //footnote[graphql-ruby-complexity][@{http://graphql-ruby.org/queries/complexity_and_depth}] 183 | 184 | それでは、試しにコスト計算をしてみます。 185 | @{complexity-query}のクエリについて考えます。 186 | gqlgenのデフォルトの計算だとどれが件数なのかなどは判別してくれないため、ものすごい雑に足し算されてコスト4にされてしまいます。 187 | なので、@{complexity.go}のように件数を考慮する式を自分で定義します。 188 | 189 | #@# prh:disable 190 | //list[complexity-query][試しにコスト計算してみる]{ 191 | # このクエリのコストは200 192 | query { 193 | # 10×子(20)で200 194 | eventList(first: 10) { 195 | # 10×子(2)で20 196 | circleList(first: 10) { 197 | # ↓ field1個で1、2個なので2 198 | id 199 | name 200 | } 201 | } 202 | } 203 | //} 204 | 205 | #@# prh:disable 206 | //list[complexity.go][件数指定のある箇所はそれを子要素のコストにかける]{ 207 | #@mapfile(../code/best-practice/complexity-sample/complexity.go) 208 | package complexity_sample 209 | 210 | const ComplexityLimit = 200 211 | 212 | func NewComplexity() ComplexityRoot { 213 | 214 | f := func(childComplexity int, first *int) int { 215 | if first == nil { 216 | // 指定無しはエラーにしたいけどそれはResolver側に任せる 217 | return 1 + childComplexity 218 | } 219 | 220 | return *first * childComplexity 221 | } 222 | 223 | complexityRoot := ComplexityRoot{} 224 | complexityRoot.Query.EventList = f 225 | complexityRoot.Event.CircleList = f 226 | 227 | return complexityRoot 228 | } 229 | #@end 230 | //} 231 | 232 | めんどくさいですがこの定義を与えてやるとクエリ実行前にいい感じにコスト計算を行ってくれるようになります。 233 | 234 | なんぼなんでも手実装はめんどくさいのでもうちょっと楽な仕組みを考えたいところではあります。 235 | struct tagからコスト設定を読むとか、特定のネーミングルールのときにコスト計算を自動化するとかしたいですね。 236 | また、深い箇所のフィールド数を増やすとコストにモロに響くので、DBから一括で取り出せるフィールドについてはコスト計算を甘くしたいなどの欲求があります。 237 | これらは今のgqlgenではサポートされておらず、将来的には僕が頑張るかもしれないけど今のところやる余力がないので先延ばしです! 238 | 239 | さらにいろいろ知りたい場合、How to GraphQLの解説@{hot-to-graphql-security}にはこの話題に限らず秀逸な解説が多いので目を通してみるとよいでしょう。 240 | 241 | //footnote[hot-to-graphql-security][@{https://www.howtographql.com/advanced/4-security/}] 242 | 243 | //comment{ 244 | TODO gqlgenの実装について 245 | * スタッフの場合無制限に実行させるオプションがほしい 246 | * クエリのcomplexityをロギングする方法がほしい 247 | * GitHub方式の計算方法がほしい 248 | * 手で頑張ればいけそうな気がするが 249 | * struct tagからコスト読み取るやつほしい 250 | * Relay Connectionの仕様に則ってそれっぽい計算を自動でやってくれる仕組みほしい 251 | //} 252 | 253 | == ユーザの認証について 254 | 255 | GraphQLには認証周りの仕様が存在していません。 256 | というわけでCookieでセッションを使うなりOAuth2を使うなり好きなものをセレクトしましょう。 257 | 258 | 個人的にはOAuth2を採用していきたいところです。 259 | モバイルアプリを考えるとCookieはちょっと…。 260 | 今のところ、Goで自分が@{IdP,Identity Provider}になれる楽な方法は模索中です。 261 | 仕事の都合で前に検討した時はory/fosite@{fosite}をベースに実装するのがよさそう…という感じでした。 262 | 263 | //footnote[fosite][@{https://github.com/ory/fosite}] 264 | 265 | いい感じのソリューションを見つけた人はぜひサンプルコードのあるリポジトリのURLを筆者までお伝え下さい。 266 | 267 | == 無理をしない型定義 268 | 269 | REST APIで色々と"頑張った"型定義をしていた場合、GraphQL上で辛い目にあうことがあります。 270 | 具体的に、技術書典では@{CircleExhibitInfo}と@{CircleExhibitInfoSecret}という2つのKind@{whats-kind}があります。 271 | 1つは公開情報やら何やらをもつKind、もう片方がサークル主の個人情報などをもつKindです。 272 | プログラミングミスで見えてはいけないものが見えてしまう危険性を減らすために分割しています。 273 | さて、REST APIではこの2つをembeddedしたsturctをJSONにして返しています。 274 | つまり、この2つを合体させたJSONが返るわけです。 275 | 276 | //footnote[whats-kind][KindはDatastore用語で、ざっくりテーブルと同じ意味だと思ってください] 277 | 278 | これに対して、さらにユーザの種類による可視性の制御が入ります。 279 | たとえば、サークルが入力している持ち込み部数は現時点では外部に公開していません。 280 | これは、サークル主とスタッフ権限持ちの人間のみが見ることができます。 281 | 282 | さて、GraphQLにこれを落とす時、どうモデリングするのがよいでしょうか? 283 | 結論からいうと、Directiveを使って制御しつつ、Kindの構成を素直に反映した構造にするのが正解のようです。 284 | 285 | 筆者がgqlgenに取り組み始めた時はDirectiveサポートがまだなかったので、色々と試行錯誤しました。 286 | CircleExhibitInfoを可視性毎にCircle、CircleForOwner、CircleForStaffに分割し提供するようにしてみました。 287 | この構成の問題点は前述のRelay Global Object Identificationと組み合わせた時が辛いです。 288 | DB上は同じモノを可視性に応じて別々のIDを振るというトリッキーな設計にする必要があり、狂気を感じます。 289 | 単純にハンドリングするコードを書く手間がすごいので普通に心が折れます。 290 | 291 | で、心が折れたのでgqlgenがDirectiveをサポートするための手伝いをして、Directiveを使うことにしました。 292 | Directiveであれば、統一的な仕組みでフィールド単位で見せたり見せなかったりを制御しやすいです。 293 | 294 | == Directiveを使い倒せ 295 | 296 | というわけでDirectiveを使いましょう。 297 | gqlgenでのDirectiveについては@{https://gqlgen.com/reference/directives/}で確認してください。 298 | 挙動は実際に自分で試して確かめてください。 299 | 300 | gqlgenのDirectiveを実装する関数に、next関数が渡されます。 301 | これを呼ぶと次のResolverを呼びにいってしまいます。 302 | つまり、アクセス制御を行うのであれば基本はnextを呼ぶ前に行うべきです。 303 | 特に、Mutationのときにそれをやると副作用が発生した後に処理を行うことになり、意味がありません。 304 | しっかりテストしよう! 305 | 306 | Directiveは色々な用途に使うことができますが、筆者の主な用途は可視性制御です。 307 | @{has-role-directive}のように@{@hasRole}というDirectiveを用意しています。 308 | 先のCircleExhibitInfoとSecretの例では、現時点ではCircleExhibitInfoに@{secret: CircleExhibitInfoSecret @hasRole(requires: RESOURCE_OWNER)}というフィールドをもたせています。 309 | これにより、サークル主かスタッフしか個人情報系のデータを参照できないようにしているわけです。 310 | 311 | //list[has-role-directive][hasRoleで権限制御]{ 312 | enum Role { 313 | PUBLIC 314 | LOGGED_IN 315 | RESOURCE_OWNER 316 | STAFF 317 | } 318 | 319 | directive @hasRole(requires: Role) on OBJECT | FIELD_DEFINITION 320 | //} 321 | 322 | さて、Directiveを使うことでSchema上のどのフィールドがどういう制御を利用しているかが自動的にドキュメント化されることになります。 323 | これはクエリを書く人にとっても、そのフィールドにアクセスするにはどういう権限が必要かが一目瞭然です。 324 | まさにGraphQLらしい、GraphiQL上で完結する素晴らしい方法…かと思いきや、どういうDirectiveがあるかはInstrospectionでわかりますが、どこでそのDirctiveが使われているかはまったくわかりません@{directive-instrospection}。 325 | と、いうことに原稿を書いていることに気が付きました。 326 | 327 | //footnote[directive-instrospection][@{https://github.com/facebook/graphql/issues/300}] 328 | 329 | ふーざーけーるーなー! 330 | ああああああああああああああ! 331 | well documentedな計画があああああああああ! 332 | オポチュニティがたりなくなるうううううう! 333 | 折れちゃう! 334 | 折れちゃううううううううううう! 335 | 336 | 対策として、ドキュメントをしっかり書く、権限チェックエラーが発生した時のエラーメッセージをわかりやすく、親切にする、などの対応をしていきましょう。 337 | 338 | == カスタムスカラ型の扱い 339 | 340 | int64などのカスタムのスカラ型を使う場合、自前でマーシャラを用意します。 341 | JavaScript(JSONではない)の仕様として、64bit幅の整数値を数値として扱うことができません。 342 | なので適当にStringにしてやる必要があります。 343 | それが辛い人は祈ってください@{js-bigint}。 344 | しらんけど。 345 | 346 | //footnote[js-bigint][@{https://github.com/tc39/proposal-bigint}] 347 | 348 | gqlgenでの実装を見ていきます(@{custom-scalars/schema.graphql}、@{custom-scalars/custom_scalars.go}、@{custom-scalars/gqlgen.yml})。 349 | 要点は3点、スキーマ上で型を宣言する、変換用Goコードを準備する、宣言とコードを紐づけするための設定を書く、以上! 350 | 公式の説明も参照してください@{gqlgen-custom-scalars}。 351 | 352 | //list[custom-scalars/schema.graphql][カスタムのスカラ型を用意する]{ 353 | #@mapfile(../code/best-practice/custom-scalars/schema.graphql) 354 | type Query { 355 | sample: Sample 356 | } 357 | 358 | scalar Int64 359 | 360 | type Sample { 361 | id: ID! 362 | value: Int64 363 | } 364 | #@end 365 | //} 366 | 367 | //list[custom-scalars/custom_scalars.go][自分でマーシャラを定義する]{ 368 | #@mapfile(../code/best-practice/custom-scalars/custom_scalars.go) 369 | package custom_scalars 370 | 371 | import ( 372 | "fmt" 373 | "io" 374 | "strconv" 375 | 376 | "github.com/99designs/gqlgen/graphql" 377 | ) 378 | 379 | // MarshalGraphQLInt64Scalar returns int64 380 | // to GraphQL Scalar value marshaller. 381 | func MarshalGraphQLInt64Scalar(v int64) graphql.Marshaler { 382 | return graphql.WriterFunc(func(w io.Writer) { 383 | s := fmt.Sprintf(`"%d"`, v) 384 | w.Write([]byte(s)) 385 | }) 386 | } 387 | 388 | // UnmarshalGraphQLInt64Scalar returns int64 value 389 | // from GraphQL value. 390 | func UnmarshalGraphQLInt64Scalar(v interface{}) (int64, error) { 391 | switch v := v.(type) { 392 | case string: 393 | return strconv.ParseInt(v, 10, 64) 394 | case int64: 395 | return v, nil 396 | default: 397 | return 0, fmt.Errorf("%T is not a int64", v) 398 | } 399 | } 400 | #@end 401 | //} 402 | 403 | //list[custom-scalars/gqlgen.yml][設定でInt64に対して使うマーシャラの指定をする]{ 404 | #@# #@mapfile(../code/best-practice/custom-scalars/gqlgen.yml) 405 | schema: schema.graphql 406 | models: 407 | Int64: 408 | model: github.com/中略/custom-scalars.GraphQLInt64Scalar 409 | #@# #@end 410 | //} 411 | 412 | //footnote[gqlgen-custom-scalars][@{https://gqlgen.com/reference/scalars/}] 413 | 414 | 細かくカスタマイズの手法が提供されているのはとても嬉しいですね。 415 | 416 | == テストの書き方について 417 | 418 | テストを書くのがめんどくさい…! 419 | ということで、筆者がよくやるゴールデンテスティングというテスト技法を紹介します。 420 | ある入力Aに対して得られた出力Bを保存しておきます。 421 | そして、次のテストではまた同じ入力Aを与え、得られた出力B'がBと等しいかどうかをチェックします。 422 | こうすることにより、出力が変わっていないことを簡単に確認出来、出力を変えた場合は出力を保存したファイルをすべて消して再出力すれば新しいマスタデータを作成できます。 423 | この手法は何かを入力すると、大量の出力があるような、たとえばコンパイラやGraphQLサーバのような構造に対するテスト技法として便利です。 424 | 425 | この技法のよい点は、入力データのパターンを量産するのが簡単で、出力変更時のテストパターンの修正がものすごく簡単なことです。 426 | GraphQLの場合、入力はクエリで、出力はJSONです。 427 | リゾルバの実装を増やすたびに新しいクエリを入力データのパターンに加えます。 428 | 後はテストを流すだけで新しいリゾルバの実装が利用された出力例が得られるわけです。 429 | 430 | この技法のわるい点は、差分が発生するような変更を入れ、テストデータを再生成したときのdiffを人力で検証しなければいけないことです。 431 | まぁこれは仕方がないですね。 432 | 他に、テストの入力となるクエリについて、何を確かめる目的のクエリなのかを書いておかないと後で困る場合があります。 433 | 434 | ゴールデンテスティングを実施したときの実例は@{http://bit.ly/2N8RQ4V}にあります。 435 | #@# http://bit.ly/2N8RQ4V → https://github.com/mercari/mtc2018-web/blob/fa73af6c379acfe9be2e1b0e08d22c295cbab1f5/server/gqlapi/resolver_test.go#L19 436 | 437 | 技術書典の場合、未ログインユーザ、ログインユーザ、リソースオーナー、スタッフの4種類の権限持ちに対して同一のクエリを走らせ、権限制御が正しく行われているかのテストも行っています。 438 | 439 | ゴールデンテスティング以外の、いわゆる普通の手法のテストは今の所めんどくさくてあまり実施していません。 440 | Directiveのテストは個別にみっちりと書き、Resolverについての個別のテストはサボっています。 441 | 442 | == Code to Schema vs Schema to Code 443 | 444 | gqlgenを礼賛する節です。 445 | なぜgqlgenを選ぶべきなのかというと、他によいものがないから、というのがもっとも大きな理由です。 446 | 447 | GraphQLを実装するアプローチとして、Schemaからコードを生成するか、コードでSchemaを表現するかの選択肢があります。 448 | どちらを選択するべきかは好みやチーム構造にフィットするかなどの観点がありますが、筆者は前者を選ぶことをお勧めします。 449 | 理由はいくつかあります。 450 | 451 | 1つ目はGraphQLのSchemaを変更するのが誰かわからないこと。 452 | Go言語でSchemaを表現した場合、Schemaを変更できるのはGo言語での開発を行える人のみです。 453 | これはSchemaの変更とResolverの実装タイミングが不可分になるからです@{job-change}。 454 | GraphQLのSchemaそのものの修正であれば、サーバ側やクライント側のコードの変更タイミングを別にし、独立して追いかけることが可能になるでしょう。 455 | 456 | //footnote[job-change][この辺りの考え方はチームメンバーがサーバもフロントもできたTG社から、分業しないと開発が成立しない規模になっているメル社に転職したことによる考え方の変化がありますね。自分のことながら興味深いことです。] 457 | 458 | 2つ目は、Go言語がDSLとして使うのには十分に柔軟ではないことです。 459 | @{graphql-go/graphql}@{graphql-go/graphql}などのコードにてSchemaを表現する形式のサンプルコードを見ると、なかなか苦しそうに見えますし、type safeなようにも見えません。 460 | さらに、Go言語は@{go:generate}などのコード生成を便利に活用する文化があり、そのための標準ライブラリも他の多くの言語に比べると圧倒的に充実しています。 461 | よって、Schemaからコードを生成するアプローチを取るべき、となります。 462 | 当然、他の言語ではまた別の選択肢があることでしょう。 463 | 464 | //footnote[graphql-go/graphql][@{https://github.com/graphql-go/graphql}] 465 | 466 | 以上の理由により、筆者はSchemaからコードを生成するアプローチであり、もっとも高機能なgqlgen@{gqlgen-contribute}を選択したわけです。 467 | 468 | //footnote[gqlgen-contribute][もっとも僕が目をつけた時点では十分に高機能ではなかったのですが僕がPRを送りまくったりして使い心地をよくしておきました(ドヤ)] 469 | 470 | == BFF vs 愚直実装 471 | 472 | @{Backend for Frontend,BFF}と愚直にDBと繋ぎこんだ実装について考えてみます。 473 | BFFのレイヤでREST APIをラップする実装と、APIサーバで直接DBとの繋ぎ込みを行うパターンです。 474 | この構成は今のところモノリシックな構造への対応を念頭においており、いわゆるMicroservice的構成についてはまだ知見がありません。 475 | 476 | さて、これについては筆者は既存REST APIに対してラップをするより、直接APIサーバに組み込みDB周りのコードに手を加えたほうがよいと考えています。 477 | これは単に実装の手間の問題で、エラーメッセージの改善やN+1問題への対処などを考えるとREST API側の変更もかなり必要になります。 478 | そう考えるともうAPIサーバに組み込んじゃったほうが手間が少ない…という感じです。 479 | REST APIの実装とDB周りのコードをシェアしてしまったほうが手間が少ないですし、キャッシュなどの構造も共有できたほうが都合がよいです。 480 | 481 | N+1問題に対処しない場合、REST APIコールがアホみたいに増えて1個のGraphQLクエリを処理するのに1000回以上のHTTP GETが発生したりしたことがありました。 482 | それでも性能的には2倍程度の酷さで済んだのでAppEngineの強さを実感しましたが不健全であることは間違いありません。 483 | 484 | お仕事的にはMicroserviceバリバリなのである程度ナレッジがたまらないといけないところではあります。 485 | この場合、GraphQLサーバとして動作するBFFを組み、裏側のAPIサーバにはBatchGet用のAPIを生やし、通信はGraphQLかgRPCで行う、とするのが良さそうに思えます。 486 | 487 | 基本方針としてRPCではGetは不要ですべてBatchGet相当で最初から実装しておけ!というのがあるんですがつい忘れがちです。 488 | GetはあってもいいけどBatchGetのラッパにする。 489 | これを守っておけば楽ができたのに…と思うことが多々ありますね。 490 | 491 | サーバ間通信はミドルウェアの仕様やツールが充実しているgRPCでいいかと思うんですが、DBの構造とマッチングさせた構成を考える場合サーバ間通信もGraphQLのほうがいいのかも…という気がします。 492 | その場合、GraphQL to GraphQLマッパがあったほうが便利そうですが今のところ便利なツールは存在していません。 493 | 494 | というかGoからクエリ投げるのがそもそもめんどくさい! 495 | ついでにいうとinterfaceがフィールド持てないのも辛い。 496 | 497 | == お前は本当に並列に対して真剣に取り組んでいるのか? 498 | 499 | 俺は並列に対して真剣のつもりだったけど全然ガバガバだった…という話です。 500 | 筆者は@{https://godoc.org/go.mercari.io/datastore,go.mercari.io/datastore}というライブラリを作って、使っています。 501 | このライブラリはDatastoreに対する操作を一時的に積み立て、バッチ処理化する仕組みがあります。 502 | gqlgenは各Resolverをgoroutineを用いて並列化するため、この仕組と実によくマッチしていて何も考えずにバッチ化処理しておけば複数のResolverからのDBアクセスが1本化されます。 503 | 504 | さて問題が発生したのは、バッチ処理化した時の待ち合わせ処理です。 505 | 複数のResolverから積まれたバッチ処理を実行します。 506 | このとき、ライブラリの内部的にはロックを取って積まれた処理を取り出し、ロックを解除し、その後に積まれた処理を実行し、それが終わったら処理を返します。 507 | 508 | 問題は、積まれた処理を取り出せなかったgoroutineです。 509 | ロックを取って何も取り出さずにロックを解除し、積まれた処理はないので即座に処理を抜けます。 510 | このgoroutineが積んでいた処理は終わっていることを期待していますが、実際には別のgoroutineが処理を肩代わりして実行中です。 511 | このせいで処理が終わっていないのでstructの中身が空のままという不可解なバグが発生し、なおかつErrNoSuchEntityが発生した時もまったく別の場所からエラーが出現するのです。 512 | 513 | この問題は今では修正され、取り出した処理すべてが完了するまでロックを返さないようにし、エラーのハンドリングもバッチ処理単位に改めることで意図通りの挙動を示すようになりました。 514 | しかし、ここにたどり着くまでのデバッグが本当に大変だったのでもう二度とこの手のバグには出会いたくないです。 515 | 516 | まとめ。 517 | 今までナァナァで動いてたコードが並列処理がまともに行われるようになったことを契機に牙を向いてくる時があるが頑張って生きてくれ! 518 | 健闘を祈る! 519 | -------------------------------------------------------------------------------- /articles/book.css: -------------------------------------------------------------------------------- 1 | @media print { 2 | body { 3 | font-family: 'Noto Sans JP', sans-serif; 4 | } 5 | /* @import url(https://fonts.googleapis.com/earlyaccess/notosansjp.css); */ 6 | /* 7 | * Noto Sans JP (japanese) http://www.google.com/fonts/earlyaccess 8 | */ 9 | @font-face { 10 | font-family: 'Noto Sans JP'; 11 | font-style: normal; 12 | font-weight: 100; 13 | src: local("Noto Sans CJK JP Thin"), 14 | url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Thin.woff2) format('woff2'), 15 | url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Thin.woff) format('woff'), 16 | url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Thin.otf) format('opentype'); 17 | } 18 | @font-face { 19 | font-family: 'Noto Sans JP'; 20 | font-style: normal; 21 | font-weight: 300; 22 | src: local("Noto Sans CJK JP Light"), 23 | url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Light.woff2) format('woff2'), 24 | url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Light.woff) format('woff'), 25 | url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Light.otf) format('opentype'); 26 | } 27 | @font-face { 28 | font-family: 'Noto Sans JP'; 29 | font-style: normal; 30 | font-weight: 400; 31 | src: local("Noto Sans CJK JP Regular"), 32 | url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Regular.woff2) format('woff2'), 33 | url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Regular.woff) format('woff'), 34 | url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Regular.otf) format('opentype'); 35 | } 36 | @font-face { 37 | font-family: 'Noto Sans JP'; 38 | font-style: normal; 39 | font-weight: 500; 40 | src: local("Noto Sans CJK JP Medium"), 41 | url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Medium.woff2) format('woff2'), 42 | url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Medium.woff) format('woff'), 43 | url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Medium.otf) format('opentype'); 44 | } 45 | @font-face { 46 | font-family: 'Noto Sans JP'; 47 | font-style: normal; 48 | font-weight: 700; 49 | src: local("Noto Sans CJK JP Bold"), 50 | url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Bold.woff2) format('woff2'), 51 | url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Bold.woff) format('woff'), 52 | url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Bold.otf) format('opentype'); 53 | } 54 | @font-face { 55 | font-family: 'Noto Sans JP'; 56 | font-style: normal; 57 | font-weight: 900; 58 | src: local("Noto Sans CJK JP Black"), 59 | url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Black.woff2) format('woff2'), 60 | url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Black.woff) format('woff'), 61 | url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Black.otf) format('opentype'); 62 | } 63 | } 64 | 65 | body { 66 | page-break-before: left; 67 | counter-reset: footnote; 68 | } 69 | @media screen { 70 | :root { 71 | line-height: 1.3; 72 | } 73 | } 74 | @media print { 75 | :root { 76 | font-weight: 400; 77 | line-height: 1.6; 78 | padding-top: 1.5rem; 79 | } 80 | 81 | a { 82 | text-decoration: none; 83 | font-style: italic; 84 | } 85 | a:link, 86 | a:visited { 87 | color: black; 88 | } 89 | } 90 | 91 | section { 92 | break-before: page; 93 | break-after: page; 94 | } 95 | nav { 96 | break-before: page; 97 | break-after: page; 98 | } 99 | aside { 100 | break-before: page; 101 | break-after: page; 102 | } 103 | 104 | p.footnote { 105 | float: footnote; 106 | } 107 | a.noteref { 108 | vertical-align: super; 109 | } 110 | /* NOTE footnoteの自動採番がやりたかった奴〜〜 */ 111 | /* 112 | section { 113 | counter-reset: footnote; 114 | } 115 | p.footnote { 116 | counter-increment: footnote; 117 | } 118 | p.footnote::footnote-marker { 119 | content: '* ' counter(footnote); 120 | } 121 | a.noteref::before { 122 | content: '*' target-counter(attr(href), footnote); 123 | vertical-align: super; 124 | } 125 | */ 126 | 127 | 128 | nav.toc a::after { 129 | content: target-counter(attr(href), page); 130 | float: right; 131 | } 132 | 133 | p { 134 | text-indent: 1rem; 135 | text-align: justify; 136 | } 137 | 138 | /* .image, */ 139 | p.caption { 140 | text-align: center; 141 | text-indent: 0; 142 | } 143 | /* list, emlist, tableのキャプションは左寄せ */ 144 | .caption-code p.caption, 145 | .emlist-code p.caption { 146 | text-align: start; 147 | } 148 | p.caption::before { 149 | color: gray; 150 | content: "▲"; 151 | } 152 | .caption-code p.caption::before, 153 | .emlist-code p.caption::before, 154 | .table p.caption::before { 155 | color: gray; 156 | content: "▼"; 157 | } 158 | 159 | pre.cmd { 160 | background-color: #444; 161 | color: white; 162 | padding: 1rem 2rem; 163 | } 164 | pre.list, pre.emlist { 165 | padding: 1rem 2rem; 166 | background-color: #eee; 167 | border: solid 3px gray; 168 | border-radius: 0.3rem; 169 | } 170 | 171 | table { 172 | margin: 0 auto 2em auto; 173 | border-collapse: collapse; 174 | } 175 | table tr th { 176 | border:1px black solid; 177 | font-size: 0.9rem; 178 | padding: 0.3rem; 179 | } 180 | table tr td { 181 | border:1px black solid; 182 | font-size: 0.9rem; 183 | padding: 0.3rem; 184 | } 185 | p.tablecaption, table caption { 186 | color: #666; 187 | font-weight: bold; 188 | text-indent: 0; 189 | } 190 | div.image { 191 | text-align: center; 192 | } 193 | div.column { 194 | padding: 0.8rem 1.5rem; 195 | border: solid 3px gray; 196 | border-radius: 0.3rem; 197 | } 198 | div.column h4 { 199 | margin-top: 0; 200 | } 201 | 202 | /* ブロック系の途中で改ページされるのを避ける。本当はかかってもいいんだけど脚注がある時に背景がぶっ壊れる… */ 203 | pre.cmd { 204 | page-break-inside: avoid; 205 | } 206 | pre.list, pre.emlist { 207 | page-break-inside: avoid; 208 | } 209 | table { 210 | page-break-inside: avoid; 211 | } 212 | 213 | @page { 214 | size: A4; 215 | /* トンボ */ 216 | marks: crop cross; 217 | 218 | /* 裁ち落としのとこまで塗りたかったら pentapod本を トンボ で検索 */ 219 | } 220 | 221 | @page { 222 | margin: 20mm; 223 | } 224 | @page :left { 225 | margin-left: 30mm; 226 | padding: 1rem; 227 | 228 | @top-left { 229 | /* 本当は現在の深さ1の見出しを出したい */ 230 | /* かつ、sectionの1ページ目だったら出したくない */ 231 | content: "Now and Features"; 232 | vertical-align: bottom; 233 | border-bottom: solid 1px black; 234 | } 235 | @top-right { 236 | content: " "; /* 内容ないと枠でない */ 237 | border-bottom: solid 1px black; 238 | } 239 | @top-center { 240 | content: " "; /* 内容ないと枠でない */ 241 | border-bottom: solid 1px black; 242 | } 243 | 244 | @bottom-center { 245 | content: "←" counter(page); 246 | } 247 | } 248 | 249 | @page :right { 250 | margin-right: 30mm; 251 | padding: 1rem; 252 | 253 | @top-right { 254 | /* 本当は現在の深さ2の見出しを出したい */ 255 | /* かつ、sectionの1ページ目だったら出したくない */ 256 | content: "Now and Features"; 257 | vertical-align: bottom; 258 | border-bottom: solid 1px black; 259 | } 260 | @top-left { 261 | content: " "; /* 内容ないと枠でない */ 262 | border-bottom: solid 1px black; 263 | } 264 | @top-center { 265 | content: " "; /* 内容ないと枠でない */ 266 | border-bottom: solid 1px black; 267 | } 268 | 269 | @bottom-center { 270 | content: counter(page) "→"; 271 | } 272 | } 273 | 274 | /* NOTE これイケるのでは?と思ったけどダメだった 275 | @page tomorrowkey:right { 276 | @top-right { 277 | content: "ともっきーだよー"; 278 | } 279 | } 280 | section.tomorrowkey { 281 | page: tomorrowkey; 282 | } 283 | */ 284 | 285 | /* NOTE これもイケるのでは?と思ったけどサポートされてなさそう 286 | @page :right { 287 | @top-right { 288 | content: element(chaptitle); 289 | } 290 | } 291 | section h1 { 292 | position: running(chaptitle); 293 | } 294 | */ 295 | 296 | @page :first { 297 | padding-top: 0mm; 298 | } 299 | 300 | .width-010per { width: 10%; } 301 | .width-020per { width: 20%; } 302 | .width-025per { width: 25%; } 303 | .width-030per { width: 30%; } 304 | .width-033per { width: 33%; } 305 | .width-040per { width: 40%; } 306 | .width-050per { width: 50%; } 307 | .width-060per { width: 60%; } 308 | .width-067per { width: 67%; } 309 | .width-070per { width: 70%; } 310 | .width-075per { width: 75%; } 311 | .width-080per { width: 80%; } 312 | .width-090per { width: 90%; } 313 | .width-100per { width: 100%; } 314 | -------------------------------------------------------------------------------- /articles/catalog.yml: -------------------------------------------------------------------------------- 1 | PREDEF: 2 | - preface.re 3 | 4 | CHAPS: 5 | - introduction.re 6 | - github-v4.re 7 | # - graphql-spec.re 8 | # - gqlgen.re 9 | - best-practice.re 10 | # - system-operation.re 11 | 12 | POSTDEF: 13 | - afterword.re 14 | # - contributors.re 15 | -------------------------------------------------------------------------------- /articles/config.yml: -------------------------------------------------------------------------------- 1 | # review-epubmaker向けの設定ファイルの例。 2 | # yamlファイルをRe:VIEWファイルのある場所に置き、 3 | # 「review-epubmaker yamlファイル」を実行すると、.epubファイルが 4 | # 生成されます。 5 | # このファイルはUTF-8エンコーディングで記述してください。 6 | 7 | # この設定ファイルでサポートするRe:VIEWのバージョン番号。 8 | # major versionが違うときにはエラーを出す。 9 | review_version: 2.0 10 | 11 | # ほかの設定ファイルの継承を指定できる。同じパラメータに異なる値がある場合は、 12 | # 呼び出し元の値が優先される。 13 | # A.yml、B.ymlのパラメータを継承する例。A.ymlとB.ymlに同じパラメータがある 14 | # 場合、B.ymlの値が優先される。さらに今このファイルに同じパラメータがあるなら、 15 | # その値がB.ymlよりも優先される。 16 | # 同様にA.yml、B.yml内でさらにinherit:パラメータを使うこともできる。 17 | # inherit: ["A.yml", "B.yml"] 18 | 19 | # ブック名(ファイル名になるもの。ASCII範囲の文字を使用) 20 | bookname: graphql-with-go-book 21 | # 記述言語。省略した場合はja 22 | language: ja 23 | 24 | # 書名 25 | # 読みを入れる例 booktitle: {name: "Re:VIEW EPUBサンプル", file-as: "リビューイーパブサンプル"} 26 | booktitle: "GraphQLサーバをGo言語で作る" 27 | 28 | # 著者名。「, 」で区切って複数指定できる 29 | # 読みを入れる例 aut: [{name: "青木峰郎", file-as: "アオキミネロウ"}, {name: "武藤健志", file-as: "ムトウケンシ"}, {name: "高橋征義", file-as: "タカハシマサヨシ"}, {name: "角征典", file-as: "カドマサノリ"}] 30 | aut: ["ひかる黄金わかめ帝国編"] 31 | 32 | # 以下はオプション 33 | # 以下はオプション(autと同じように配列書式で複数指定可能)。 34 | # 読みの指定はaut:の例を参照。 35 | # a-が付いているものはcreator側、 36 | # 付いていないものはcontributor側(二次協力者)に入る 37 | # a-adp, adp: 異なるメディア向けに作り直した者 38 | # a-ann, ann: 注釈記述者 39 | # a-arr, arr: アレンジした者 40 | # a-art, art: グラフィックデザインおよび芸術家 41 | # a-asn, asn: 関連・かつての所有者・関係者 42 | # a-aqt, aqt: 大きく引用された人物 43 | # a-aft, aft: 後書き・奥付の責任者 44 | # a-aui, aui: 序論・序文・前書きの責任者 45 | # a-ant, ant: 目録責任者 46 | # a-bkp, bkp: メディア制作責任者 47 | # a-clb, clb: 限定参加または補足者 48 | # a-cmm, cmm: 解釈・分析・考察者 49 | # a-csl, csl: 監修者 50 | # a-dsr, dsr: デザイナ 51 | # a-edt, edt: 編集者 52 | edt: ["vvakame"] 53 | # a-ill, ill: イラストレータ 54 | # a-lyr, lyr: 歌詞作成者 55 | # a-mdc, mdc: メタデータセットの一次的責任者 56 | # a-mus, mus: 音楽家 57 | # a-nrt, nrt: 語り手 58 | # a-oth, oth: その他 59 | # a-pht, pht: 撮影責任者 60 | # a-pbl, pbl: 出版社(発行所) 61 | pbl: ひかる黄金わかめ帝国 62 | # a-prt, prt: 印刷所 63 | # prt: TechBooster 64 | # a-red, red: 項目の枠組起草者 65 | # a-rev, rev: 評論者 66 | # a-spn, spn: 援助者 67 | # a-ths, ths: 監督者 68 | # a-trc, trc: 筆記・タイプ作業者 69 | # a-trl, trl: 翻訳者 70 | 71 | # 刊行日(省略した場合は実行時の日付) 72 | date: 2018-10-08 73 | # 発行年月。YYYY-MM-DD形式による配列指定。省略した場合はdateを使用する 74 | # 複数指定する場合は次のように記述する 75 | # [["初版第1刷の日付", "初版第2刷の日付"], ["第2版第1刷の日付"]] 76 | # 日付の後ろを空白文字で区切り、任意の文字列を置くことも可能。 77 | history: [["2018-10-08 技術書典5版 v1.0.0"]] 78 | # 権利表記(配列で複数指定可) 79 | # rights: (C) 2016 Re:VIEW Developers 80 | rights: (C) 2018 ひかる黄金わかめ帝国 81 | # description: 説明 82 | # subject: 短い説明用タグ(配列で複数指定可) 83 | # type: 書籍のカテゴリーなど(配列で複数指定可) 84 | # format: メディアタイプおよび特徴(配列で複数指定可) 85 | # source: 出版物生成の重要なリソース情報(配列で複数指定可) 86 | # relation: 補助的リソース(配列で複数指定可) 87 | # coverage: 内容の範囲や領域(配列で複数指定可) 88 | 89 | # デバッグフラグ。nullでないときには一時ファイルをカレントディレクトリに作成し、削除もしない 90 | debug: null 91 | 92 | # 固有IDに使用するドメイン。省略した場合は時刻に基づくランダムUUIDが入る 93 | urnid: urn:uid:https://github.com/vvakame/graphql-with-go-book 94 | # 95 | # ISBN。省略した場合はurnidが入る 96 | # isbn: null 97 | # 98 | # HTMLファイルの拡張子(省略した場合はhtml) 99 | htmlext: html 100 | # 101 | # CSSファイル(配列で複数指定可、yamlファイルおよびRe:VIEWファイルを置いたディレクトリにあること) 102 | stylesheet: ["style.scss"] 103 | 104 | # ePUBのバージョン (2か3) 105 | epubversion: 3 106 | # 107 | # HTMLのバージョン (4か5。epubversionを3にしたときには5にする) 108 | htmlversion: 5 109 | 110 | # 目次として抽出する見出しレベル 111 | toclevel: 3 112 | 113 | # 採番の設定。採番させたくない見出しには「==[nonum]」のようにnonum指定をする 114 | # 115 | # 本文でセクション番号を表示する見出しレベル 116 | secnolevel: 3 117 | 118 | # 以下のsecnolevelはまだ未実装。 119 | # 前付でセクション番号を表示する見出しレベル(未実装) 120 | # pre_secnolevel: 0 121 | # 122 | # 後付(付録)でセクション番号を表示する見出しレベル(未実装) 123 | # post_secnolevel: 1 124 | # 125 | # 部番号を表示する見出しレベル(未実装) 126 | # part_secnolevel: 1 127 | 128 | # 本文中に目次ページを作成するか。省略した場合はnull (作成しない) 129 | toc: true 130 | 131 | # EPUB2標準の目次(NCX)以外に物理目次ファイルを作成するか。省略した場合はnull (作成しない) 132 | # ePUB3においてはこの設定によらず必ず作成される 133 | # mytoc: true 134 | 135 | # 表紙にするHTMLファイル。ファイル名を指定すると表紙として入る 136 | # cover: null 137 | # 138 | # 表紙に配置し、書籍の影絵にも利用する画像ファイル。省略した場合はnull (画像を使わない)。画像ディレクトリ内に置いてもディレクトリ名は不要(例: cover.jpg) 139 | # coverimage: cover.png 140 | # 141 | # 表紙の後に大扉ページを作成するか。省略した場合はnull (作成しない) 142 | titlepage: true 143 | # 144 | # 自動生成される大扉ページを上書きするファイル。ファイル名を指定すると大扉として入る 145 | # titlefile: null 146 | # 147 | # 原書大扉ページにするHTMLファイル。ファイル名を指定すると原書大扉として入る 148 | # originaltitlefile: null 149 | # 150 | # 権利表記ページファイル。ファイル名を指定すると権利表記として入る 151 | # creditfile: null 152 | 153 | # 奥付を作成するか。デフォルトでは作成されない。trueを指定するとデフォルトの奥付、ファイル名を指定するとそれがcolophon.htmlとしてコピーされる 154 | colophon: true 155 | 156 | # 裏表紙ファイル (画像はcoversまたはimagesに配置する)。ファイル名を指定すると裏表紙として入る 157 | # backcover: null 158 | 159 | # プロフィールページファイル。ファイル名を指定すると著者紹介として入る 160 | # profile: null 161 | # プロフィールページの目次上の見出し 162 | # profiletitle: 著者紹介 163 | 164 | # 広告ファイル。ファイル名を指定すると広告として入る 165 | # advfile: null 166 | 167 | # 取り込む画像が格納されているディレクトリ。省略した場合は以下 168 | # imagedir: images 169 | 170 | # 取り込むフォントが格納されているディレクトリ。省略した場合は以下 171 | # fontdir: fonts 172 | 173 | # imagedir内から取り込まれる対象となるファイル拡張子。省略した場合は以下 174 | # image_ext: ["png", "gif", "jpg", "jpeg", "svg", "ttf", "woff", "otf"] 175 | 176 | # fontdir内から取り込まれる対象となるファイル拡張子。省略した場合は以下 177 | # font_ext: ["ttf", "woff", "otf"] 178 | 179 | # ソースコードハイライトを利用する (pygmentsには外部gemが必要) 180 | # highlight: 181 | # html: "rouge" 182 | # html: "pygments" 183 | # latex: "listings" 184 | 185 | # カタログファイル名を指定する 186 | # catalogfile: catalog.yml 187 | 188 | # 1ページの行数文字数と1kbごとのページ数を用紙サイズで指定する(A5 or B5)。 189 | # page_metric: A5 190 | # 191 | # あるいは、配列で指定することもできる 192 | # 各数字の意味は、順にリストの行数、リストの1行字数、テキストの行数、テキストの1行字数、1kバイト毎のページ数 193 | # page_metric: [40,80,40,80,2] 194 | 195 | # ページ送りの送り方向、page-progression-directionの値("ltr"|"rtl"|"default") 196 | direction: "ltr" 197 | 198 | # EPUBのOPFへの固有の追加ルール 199 | # 要素に追加する名前空間 200 | # opf_prefix: {ebpaj: "http://www.ebpaj.jp/"} 201 | # 追加する要素のプロパティとその値 202 | # opf_meta: {"ebpaj:guide-version": "1.1.3"} 203 | 204 | # 以下のパラメータを有効にするときには、 205 | # epubmaker: 206 | # パラメータ: 値 207 | # パラメータ: 値 208 | # ... 209 | # という構成にする必要がある(インデントさせる) 210 | 211 | epubmaker: 212 | # HTMLファイルの拡張子 213 | htmlext: xhtml 214 | # 215 | # 目次を要素の階層表現にしない。省略した場合(null)は階層化する。 216 | # 特に部扉が入るなどの理由で、構成によっては階層化目次でepubcheckに 217 | # パスしない目次ができるが、そのようなときにはこれをtrueにする 218 | # flattoc: null 219 | # 220 | # 目次のインデントレベルをスペース文字で表現する(flattocがtrueのときのみ) 221 | # flattocindent: true 222 | # 223 | # NCX目次の見出しレベルごとの飾り(配列で設定)。EPUB3ではNCXは作られない 224 | # ncxindent: 225 | #- 226 | #- - 227 | # フックは、各段階で介入したいときのプログラムを指定する。自動で適切な引数が渡される 228 | # プログラムには実行権限が必要 229 | # ファイル変換処理の前に実行するプログラム。スタイルシートのコンパイルをしたいときなどに利用する。 230 | # 渡される引数1=作業用展開ディレクトリ 231 | # hook_beforeprocess: null 232 | # 233 | # 前付の作成後に実行するプログラム。作業用展開ディレクトリにある目次ファイル(toc-html.txt)を操作したいときなどに利用する。 234 | # 渡される引数1=作業用展開ディレクトリ 235 | # hook_afterfrontmatter: null 236 | # 237 | # 本文の変換後に実行するプログラム。作業用展開ディレクトリにある目次ファイル(toc-html.txt)を操作したいときなどに利用する。 238 | # 渡される引数1=作業用展開ディレクトリ 239 | # hook_afterbody: null 240 | # 241 | # 後付の作成後に実行するプログラム。作業用展開ディレクトリにある目次ファイル(toc-html.txt)を操作したいときなどに利用する。 242 | # 渡される引数1=作業用展開ディレクトリ 243 | # hook_afterbackmatter: null 244 | # 245 | # 画像およびフォントをコピーした後に実行するプログラム。別の画像やフォントを追加したいときなどに利用する。 246 | # 渡される引数1=作業用展開ディレクトリ 247 | # hook_aftercopyimage: null 248 | # 249 | # ePUB zipアーカイブ直前に実行するプログラム。メタ情報などを加工したいときなどに利用する。 250 | # 渡される引数1=ePUB準備ディレクトリ 251 | # hook_prepack: null 252 | # 253 | # 変換したHTMLファイルおよびCSSを解析して厳密に使用している画像ファイルだけを取り込むか。デフォルトはnull(imagesディレクトリすべてを取り込む) 254 | # なお、フォント、カバー、広告についてはこの設定によらずディレクトリ内のものがすべて取り込まれる 255 | # verify_target_images: null 256 | # 257 | # verify_target_imagesがtrueの状態において、解析で発見されなくても強制的に取り込むファイルの相対パスの配列 258 | # force_include_images: [] 259 | # 260 | # Re:VIEWファイル名を使わず、前付にpre01,pre02...、本文にchap01,chap02l...、後付にpost01,post02...という名前付けルールにするか 261 | # rename_for_legacy: null 262 | # 263 | # ePUBアーカイブの非圧縮実行 264 | # zip_stage1: "zip -0Xq" 265 | # 266 | # ePUBアーカイブの圧縮実行 267 | # zip_stage2: "zip -Xr9Dq" 268 | # 269 | # ePUBアーカイブに追加するパス(デフォルトはmimetype、META-INF、OEBPS) 270 | # zip_addpath: null 271 | # 272 | # EPUBで表紙をコンテンツに含めるか。デフォルトでは作成されない。yesにするとiBooks等でも最初に表紙が表示されるようになる 273 | # cover_linear: null 274 | # 275 | # @タグでの外部リンクを禁止し、地の文にする(falseで禁止する) 276 | # externallink: true 277 | # 278 | # epubmaker:階層を使うものはここまで 279 | 280 | # LaTeX用のスタイルファイル(styディレクトリ以下に置くこと) 281 | # tatsumacroは、電子書籍版の制作に利用する 282 | # texstyle: tatsumacro 283 | texstyle: techbooster-doujin 284 | # 285 | # LaTeX用のdocumentclassを指定する 286 | texdocumentclass: ["jsbook", "oneside,14pt,uplatex"] 287 | # texdocumentclass: ["jsbook", "b5j,twoside,openany,uplatex"] 288 | # texdocumentclass: ["jsbook", "a5j,twoside,openany,uplatex,9pt"] 289 | # 290 | # LaTeX用のコマンドを指定する 291 | # texcommand: "uplatex" 292 | # 293 | # LaTeXのコマンドに渡すオプションを指定する 294 | # texoptions: null 295 | # 296 | # LaTeX用のdvi変換コマンドを指定する(dvipdfmx) 297 | # dvicommand: "dvipdfmx" 298 | # 299 | # LaTeX用のdvi変換コマンドのオプションを指定する 300 | # dvioptions: "-d 5" 301 | 302 | # 以下のパラメータを有効にするときには、 303 | # pdfmaker: 304 | # パラメータ: 値 305 | # パラメータ: 値 306 | # ... 307 | # という構成にする必要がある(インデントさせる) 308 | # 309 | pdfmaker: 310 | # 311 | # TeXコンパイル前に実行するプログラム。変換後のTeXソースを調整したいときに使用する。 312 | # 渡される引数1=作業用展開ディレクトリ、引数2=呼び出しを実行したディレクトリ 313 | # hook_beforetexcompile: null 314 | # 315 | # TeXコンパイル後に実行するプログラム。索引作業をして再度コンパイルしたいときなどに使用する。 316 | # 渡される引数1=作業用展開ディレクトリ、引数2=呼び出しを実行したディレクトリ 317 | # hook_aftertexcompile: null 318 | # 319 | # PDF(book.pdf)作成後に実行するプログラム。PDFに加工を施したいときに使用する。 320 | # 渡される引数1=作業用展開ディレクトリ、引数2=呼び出しを実行したディレクトリ 321 | # hook_afterdvipdf: null 322 | # 323 | # 画像のscale=X.Xという指定を画像拡大縮小率からページ最大幅の相対倍率に変換します。 324 | # image_scale2width: true 325 | # 326 | # 奥付を作成するか。trueを指定するとデフォルトの奥付、ファイル名を指定するとそれがcolophon.htmlとしてコピーされる 327 | colophon: true 328 | # pdfmaker:階層を使うものはここまで 329 | 330 | # 以下のパラメータを有効にするときには、 331 | # webmaker: 332 | # パラメータ: 値 333 | # パラメータ: 値 334 | # ... 335 | # という構成にする必要がある(インデントさせる) 336 | # 337 | webmaker: 338 | stylesheet: ["style.css","style-web.css"] 339 | -------------------------------------------------------------------------------- /articles/epub_style.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | /* Tatujin-Publishing */ 3 | /* Style sheet for epub */ 4 | /* Ver.0.8b1 */ 5 | 6 | /* 7 | Scale & Rhythm 8 | line-height 1.6 9 | 16px = 1em 10 | x:p:h1:h2:h3 = 12px:14px:16px:24px:30px 11 | */ 12 | * { 13 | } 14 | body { 15 | margin: 0; 16 | padding: 0; 17 | font-size: 1em; 18 | line-height:1.6; 19 | font-family: "ShinGoPro-Regular","ShinGo-Regular", sans-serif; 20 | /* 21 | word-break: normal; 22 | -webkit-line-break: after-white-space; 23 | */ 24 | } 25 | p, ul, ol, dl, pre, table { 26 | font-family: "ShinGo Regular","ShinGo R","新ゴR","新ゴ R", sans-serif; 27 | font-size: 0.875em; 28 | } 29 | /* Heading */ 30 | h1 { 31 | margin: 0 0 3em; 32 | padding: 0.5em 0 0; 33 | border-top: 14px #326450 solid; 34 | text-align: left; 35 | font-size: 1.875em; 36 | font-weight: bold; 37 | } 38 | h2 { 39 | margin: 3em 0 0.5em; 40 | padding: 0.5em 0 0; 41 | border-top: 2px #326450 solid; 42 | text-align: left; 43 | font-size: 1.5em; 44 | font-weight: bold; 45 | } 46 | h3 { 47 | margin: 3em 0 0.5em; 48 | padding: 0; 49 | text-align: left; 50 | font-size: 1em; 51 | font-weight: bold; 52 | } 53 | h4, h5, h6 { 54 | margin:0.7em 0; 55 | padding: 0; 56 | text-align: left; 57 | line-height: 1.6; 58 | font-weight: bold; 59 | } 60 | /* Paragraph */ 61 | p { 62 | margin:0.7em 0; 63 | padding: 0; 64 | text-align: left; 65 | text-indent: 1em; 66 | line-height: 1.6; 67 | } 68 | div.lead p { 69 | color: #666; 70 | line-height: 1.6; 71 | font-size: 0.75em; 72 | } 73 | /* List */ 74 | ul, ol { 75 | margin: 2em 0 2em 2em; 76 | padding: 0; 77 | list-style-position: outside; 78 | } 79 | ul > li, 80 | ol > li { 81 | margin: 0 0 0.7em 0; 82 | padding: 0; 83 | line-height: 1.6; 84 | } 85 | dl { 86 | margin: 2em 0; 87 | padding: 0; 88 | } 89 | dt { 90 | margin: 0; 91 | padding: 0; 92 | font-weight: bold; 93 | } 94 | dd { 95 | margin: 0 0 1em 2em; 96 | padding: 0; 97 | line-height: 1.6; 98 | } 99 | /* Table 100 | p.tablecaptionではなく 101 | table caption {}を使う方が良いかも? 102 | */ 103 | table { 104 | margin: 0 auto 2em auto; 105 | border-collapse: collapse; 106 | } 107 | table tr th { 108 | background-color: #eee; 109 | border:1px #aaa solid; 110 | font-size: 0.75em; 111 | font-weight: normal; 112 | } 113 | table tr td { 114 | padding: 0.3em; 115 | border:1px #aaa solid; 116 | font-size: 0.75em; 117 | } 118 | p.tablecaption, table caption { 119 | margin: 0; 120 | color: #666; 121 | font-size: 0.75em; 122 | font-weight: bold; 123 | text-indent: 0; 124 | } 125 | /* Quote */ 126 | blockquote { 127 | margin: 2em 0 2em 2em; 128 | padding: 0.3em 1em; 129 | border: 1px #aaa solid; 130 | } 131 | /* Column Block */ 132 | div.column { 133 | margin: 2em 0 2em 2em; 134 | padding: 0.3em 1em; 135 | background-color: #eee; 136 | -webkit-border-radius: 0.7em; 137 | } 138 | div.column *{ 139 | margin:0.7em 0; 140 | } 141 | div.column ul, 142 | div.column ol { 143 | list-style-position: inside; 144 | } 145 | /* Code Block */ 146 | /* 147 | ※シンプルにできるかも 148 | div.code {} 149 | div.code pre.list, 150 | div.code pre.cmd {} 151 | div.code p.caption {} 152 | */ 153 | div.code, div.caption-code, div.source-code, div.emlist-code, div.emlistnum-code { 154 | margin: 1em 0 2em 2em; 155 | padding: 0; 156 | } 157 | pre.emlist, pre.source, pre.list { 158 | margin: 0; 159 | padding: 5px; 160 | border: 1px #aaa solid; 161 | } 162 | div p.caption { 163 | margin: 0; 164 | color: #666; 165 | font-size: 0.75em; 166 | font-weight: bold; 167 | } 168 | div.cmd-code pre.cmd { 169 | margin: 0; 170 | padding: 5px; 171 | color: #ccc; 172 | font-weight: bold; 173 | background-color: #444; 174 | -webkit-border-radius: 0.5em; 175 | } 176 | pre.cmd, pre.emlist, pre.list, pre.source { 177 | white-space: pre-wrap; 178 | } 179 | 180 | /* Image Block */ 181 | /* div.image p.caption {} 182 | ※captionをそろえた方が良いかも?*/ 183 | div.image { 184 | margin: 2em auto; 185 | padding: 0; 186 | } 187 | div.image img { 188 | margin: 0 auto; 189 | padding: 0; 190 | display: block; 191 | } 192 | div.image p.caption { 193 | margin: 0 auto; 194 | text-align: center; 195 | color: #666; 196 | font-size: 0.75em; 197 | font-weight: bold; 198 | text-indent: 0; 199 | } 200 | /* Footnote Block */ 201 | /* p.footnoteはいらないかも? */ 202 | div.footnote { 203 | } 204 | div.footnote p.footnote { 205 | color: #666; 206 | line-height: 1.6; 207 | font-size: 0.75em; 208 | text-indent: 0; 209 | } 210 | /* Colophon */ 211 | div.colophon { 212 | margin: 3em auto; 213 | } 214 | div.colophon p { 215 | text-indent: 0; 216 | } 217 | div.colophon p.title { 218 | font-size: 1.5em; 219 | } 220 | div.colophon table { 221 | margin: 1em 0 2em; 222 | border: none; 223 | } 224 | div.colophon table tr th { 225 | background-color: #fff; 226 | font-size: 1.2em; 227 | font-weight: normal; 228 | border: none; 229 | } 230 | div.colophon table tr td { 231 | font-size: 1.2em; 232 | font-weight: normal; 233 | border: none; 234 | } 235 | 236 | /* Inline */ 237 | a[href], 238 | a:link, 239 | a:visited { 240 | border-bottom: 1px dotted #531084; 241 | text-decoration: none; 242 | } 243 | b { 244 | font-weight: bold; 245 | } 246 | strong { 247 | font-weight: bold; 248 | } 249 | em { 250 | font-style: italic; 251 | } 252 | 253 | /** 254 | * from EBPAJ EPUB 3 File Creation Guide sample style 255 | * 256 | * cf. http://ebpaj.jp/counsel/guide 257 | */ 258 | 259 | /* image width definition(pacentage) */ 260 | .width-010per { width: 10%; } 261 | .width-020per { width: 20%; } 262 | .width-025per { width: 25%; } 263 | .width-030per { width: 30%; } 264 | .width-033per { width: 33%; } 265 | .width-040per { width: 40%; } 266 | .width-050per { width: 50%; } 267 | .width-060per { width: 60%; } 268 | .width-067per { width: 67%; } 269 | .width-070per { width: 70%; } 270 | .width-075per { width: 75%; } 271 | .width-080per { width: 80%; } 272 | .width-090per { width: 90%; } 273 | .width-100per { width: 100%; } 274 | -------------------------------------------------------------------------------- /articles/github-v4.re: -------------------------------------------------------------------------------- 1 | = GraphQLに触れる!GitHub v4 API 2 | 3 | さて、GraphQLでサーバを組む前に、GraphQLを使ってデータを取得してくることに慣れてみましょう。 4 | GraphiQL@{graphiql}という大変出来のよいプレイグラウンド(エクスプローラ)があります。 5 | GraphiQLは一瞬typoかと思いがちなんですが、GraphQLとは別のものを指します。 6 | 7 | //footnote[graphiql][@{https://github.com/graphql/graphiql}] 8 | 9 | GraphQLは仕様として、Introspectionでエンドポイントがどういうクエリや型などをもつか、GraphQL自体を使って問い合わせることができます。 10 | クエリ言語なのに、普通のプログラミング言語でいうリフレクションのような機能を持っているのです。 11 | この機能を使って、GraphiQL上でクエリを書くと入力補完が完備され、定義の参照も行うことができ、ドキュメントもすぐに閲覧できます。 12 | 実際、GraphQLを使ってクエリを試行錯誤する時にAPIの使い方や存在を調べて回ることはREST APIに比べて格段に少ないです。 13 | このおかげで、ドキュメントを自分のために積極的に書きたくなります。 14 | 15 | == GitHub v4 APIとはなんぞや 16 | 17 | さて、では早速やってみましょう。 18 | これから遊ぶ場所はGitHub v4 API@{github-v4}です。 19 | 我々エンジニアがもっともよく使うサービスの代表格、GitHubはGraphQLエンドポイントを公開されています。 20 | これを叩いて、色々試してみましょう。 21 | 22 | //footnote[github-v4][@{https://developer.github.com/v4/explorer/}] 23 | 24 | Webサイトを開き、Sign in with GitHubから認証を済ませたら準備完了です。 25 | 26 | == GraphiQL…最高や! 27 | 28 | やっていきましょう。 29 | まずは@{query-viewer}のクエリを投げてみます。 30 | すると、筆者の場合@{query-viewer-result}のデータが得られます。 31 | 32 | //list[query-viewer][viewerを調べる]{ 33 | #@mapfile(../code/github-v4/text/query-viewer.graphql) 34 | { 35 | viewer { 36 | login 37 | } 38 | } 39 | #@end 40 | //} 41 | 42 | //list[query-viewer-result][クエリの結果]{ 43 | #@mapfile(../code/github-v4/text/query-viewer.graphql.result) 44 | { 45 | "data": { 46 | "viewer": { 47 | "login": "vvakame" 48 | } 49 | } 50 | } 51 | #@end 52 | //} 53 | 54 | わかりやすいですね。 55 | クエリの構造に対応した形式のJSONが返ってきます。 56 | 57 | さて、これだけでは特に面白くもありません。 58 | 次にクエリの@{viewer}部分をCommandキーやCtrlキーを押しながらクリックしてみます(@{query-viewer})。 59 | まるでIDEのように、定義が右側に開いたり、tooltipが表示されたりして、どういう型でどういうドキュメントが書かれているかを確認することができます。 60 | 61 | //image[query-viewer][ドキュメントが参照できる]{ 62 | //} 63 | 64 | いい話だ…。 65 | viewerはUser型であることがわかります。 66 | User型にはどういうフィールドがあるのか、クエリには他にどんな要素があるのか、ドンドン連鎖的に調べていけます。 67 | 知りたいことがある時に、1つのページ内で試行錯誤できるのはストレスフリーで最高ですね。 68 | 69 | == Schemaを眺める 70 | 71 | このまま、Schemaを眺めていきましょう。 72 | 右側のDocument Explorerですが、さきほどまで色々と定義をジャンプして遊んでいたはずですので、まずは左上の@{<}マークを押して最上層まで戻ります。 73 | 74 | GitHubではOperationとして、QueryとMutationを持っているようです(@{document-explorer})。 75 | Queryはデータの取得、Mutationはデータの変更、ここにはありませんがSubscribeはデータの監視を行うことができます。 76 | 77 | //image[document-explorer][Query, Mutationが見える][scale=0.7]{ 78 | //} 79 | 80 | Queryの内容を見てみて、興味のありそうなものを適当に見て回ってみましょう。 81 | 筆者は@{repositoryOwner(login: String!): RepositoryOwner}という引数付きフィールド(@{document-query-repositoryOwner})に興味を惹かれました。 82 | さきほど、viewerを見た時にUserという型がありました、ここでの返り値がUser型ではないのはなぜでしょうか。 83 | 84 | //image[document-query-repositoryOwner][RepositoryOwnerとは…?][scale=0.7]{ 85 | //} 86 | 87 | 中身を見てみると、色々とフィールドがあり、最後にimplementations Organization, Userとなっています。 88 | なるほど、RepositoryOwnerはinterfaceで、それを実装する型としてOrganizationとUserがあるわけです。 89 | 確かにGitHubではUserの他にOrganizationがRepositoryの持ち主になる場合もあるのでした。 90 | 91 | GitHubも長い歴史があり、途中からGraphQLを導入するためにさまざまな苦労がありそうだと察することができます。 92 | 我々もやっていきましょう。 93 | 94 | == ドキュメントを元にQueryを書いてみる 95 | 96 | というわけで、GraphiQL上でクエリを書く時は入力補完で気持ちよくスイスイと書けますので、実際に書いてみましょう。 97 | 98 | 突然ですが問題です! 99 | "github" Organizationのもつメールアドレスを調べてみましょう! 100 | 101 | 解答は読者への宿題とします。 102 | 103 | == Goでクライアントを書く 104 | 105 | はい。 106 | APIの要点を理解するには3rd partyパッケージを使わずにAPIを叩いてみるのが一番!ということで、Goで適当にリクエストを投げてみます(@{go-client/main.go})。 107 | クエリと変数をPOSTで投げるだけです。 108 | 実行には環境変数に@{GITHUB_TOKEN}という名前でPersonal Access Token@{pat}をセットしておく必要があります。 109 | クエリの部分毎に必要なパーミッションは異なるので、自分で必要だと思う権限を付与しておきましょう。 110 | このサンプルコードの場合、@{read:org}の権限が必要です。 111 | 112 | //footnote[pat][@{https://github.com/settings/tokens}] 113 | 114 | //list[go-client/main.go][所定の形式でPOST投げるだけ]{ 115 | #@mapfile(../code/github-v4/go-client/main.go) 116 | package main 117 | 118 | import ( 119 | "bytes" 120 | "encoding/json" 121 | "fmt" 122 | "io/ioutil" 123 | "net/http" 124 | "net/url" 125 | "os" 126 | ) 127 | 128 | func main() { 129 | githubToken := os.Getenv("GITHUB_TOKEN") 130 | query := ` 131 | query ($login: String!) { 132 | organization(login: $login) { 133 | name 134 | email 135 | } 136 | } 137 | ` 138 | b, err := json.Marshal(struct { 139 | Query string `json:"query"` 140 | Variable map[string]interface{} `json:"variables"` 141 | }{ 142 | Query: query, 143 | Variable: map[string]interface{}{ 144 | "login": "github", 145 | }, 146 | }) 147 | if err != nil { 148 | panic(err) 149 | } 150 | 151 | endpointURL, err := url.Parse("https://api.github.com/graphql") 152 | if err != nil { 153 | panic(err) 154 | } 155 | buf := bytes.NewBuffer(b) 156 | resp, err := http.DefaultClient.Do(&http.Request{ 157 | URL: endpointURL, 158 | Method: "POST", 159 | Header: http.Header{ 160 | "Content-Type": {"application/json"}, 161 | "Authorization": {"bearer " + githubToken}, 162 | }, 163 | Body: ioutil.NopCloser(buf), 164 | }) 165 | if err != nil { 166 | panic(err) 167 | } 168 | b, err = ioutil.ReadAll(resp.Body) 169 | if err != nil { 170 | panic(err) 171 | } 172 | 173 | fmt.Println(string(b)) 174 | } 175 | #@end 176 | //} 177 | 178 | 実行結果は@{go-client/main.go:result}です。 179 | なるほどなるほど。 180 | 181 | //list[go-client/main.go:result][得られたレスポンス]{ 182 | { 183 | "data": { 184 | "organization": { 185 | "name": "GitHub", 186 | "email": "support@github.com" 187 | } 188 | } 189 | } 190 | //} 191 | 192 | ところで、GraphQLの仕様にはHTTPレベルでの仕様は決められていません。 193 | よって、今回のこのAPIの叩き方以外にも、GETメソッドで叩く方法なども存在しています。 194 | SubscriptionのためにWebSocketを使ったり、ファイルアップロードのためにmultipartを使った方法が模索されていたり、色々な亜種があります。 195 | とはいえ、基本はPOSTをつかったこのやり方が一般的です。 196 | -------------------------------------------------------------------------------- /articles/gqlgen.re: -------------------------------------------------------------------------------- 1 | = gqlgenを使ってみよう 2 | 3 | 頑張って書きたいと思ってたけど諦めた。 4 | やれば分かるよな!!な!? 5 | 6 | == なぜgqlgenか? 7 | 8 | TODO 9 | 10 | === gqlgenの特徴 11 | 12 | TODO 13 | 14 | === 他ツールとの比較 15 | 16 | TODO 17 | 18 | == gqlgenのインストール 19 | 20 | TODO 21 | 22 | == gqlgen init 23 | 24 | TODO 25 | 26 | == gqlgen gen 27 | 28 | TODO 29 | 30 | == .gqlgen.yml 31 | 32 | TODO 33 | 34 | == 生成されたコードを見てみよう 35 | 36 | TODO 37 | 38 | == Resolverの構成のしかた 39 | 40 | TODO 41 | 42 | == Directiveの構成のしかた 43 | 44 | TODO 45 | 46 | == ErrorPresenter 47 | 48 | TODO 49 | 50 | == RequestMiddleware 51 | 52 | TODO 53 | 54 | == ResolverMiddleware 55 | 56 | TODO 57 | 58 | == v0と覚悟の話 59 | 60 | TODO 61 | BREAKING CHANGEは ま ちょと覚悟しておけ 62 | godocもお察し 63 | -------------------------------------------------------------------------------- /articles/graphql-spec.re: -------------------------------------------------------------------------------- 1 | = GraphQLの仕様を学ぶ 2 | 3 | これも時間がないからカットだ! 4 | 5 | == 構文の話 6 | 7 | TODO 8 | 9 | === Operation 10 | 11 | TODO 12 | 13 | === Query 14 | 15 | TODO 16 | 17 | ==== Fragment 18 | 19 | TODO 20 | 21 | === Mutation 22 | 23 | TODO 24 | 25 | === Subscription 26 | 27 | TODO 28 | 29 | == 成功と失敗の概念 30 | 31 | TODO 32 | 200 OKとか 33 | errorsとか 34 | 35 | == トランスポートレイヤについて 36 | 37 | TODO 38 | ねぇよそんなもん! 39 | 40 | == 関連仕様 41 | 42 | TODO 43 | Relay 3兄弟 44 | ファイルアップロードとか3rd party系spec 45 | 46 | == なぜRelayを勉強するべきか 47 | 48 | TODO 49 | 50 | == FacebookとApollo 51 | 52 | TODO 53 | -------------------------------------------------------------------------------- /articles/images/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvakame/graphql-with-go-book/7e429d39af95678a4395671ac095c1a5c95ea929/articles/images/.gitkeep -------------------------------------------------------------------------------- /articles/images/github-v4/document-explorer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvakame/graphql-with-go-book/7e429d39af95678a4395671ac095c1a5c95ea929/articles/images/github-v4/document-explorer.png -------------------------------------------------------------------------------- /articles/images/github-v4/document-query-repositoryOwner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvakame/graphql-with-go-book/7e429d39af95678a4395671ac095c1a5c95ea929/articles/images/github-v4/document-query-repositoryOwner.png -------------------------------------------------------------------------------- /articles/images/github-v4/query-viewer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvakame/graphql-with-go-book/7e429d39af95678a4395671ac095c1a5c95ea929/articles/images/github-v4/query-viewer.png -------------------------------------------------------------------------------- /articles/images/introduction/query-and-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvakame/graphql-with-go-book/7e429d39af95678a4395671ac095c1a5c95ea929/articles/images/introduction/query-and-data.png -------------------------------------------------------------------------------- /articles/images/introduction/tbf-chain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvakame/graphql-with-go-book/7e429d39af95678a4395671ac095c1a5c95ea929/articles/images/introduction/tbf-chain.png -------------------------------------------------------------------------------- /articles/introduction.re: -------------------------------------------------------------------------------- 1 | = GraphQLってなんなのさ 2 | 3 | まずはGraphQLが何なのか、なぜ勉強してみるべきなのかを考えます。 4 | GraphQLは銀の弾丸ではないですが、フロント側の開発体験を改善するものであると筆者は考えています。 5 | 6 | GraphQLはGraph Query Languageの名のとおり、グラフ構造に対するクエリ言語です。 7 | 大本をFacebookが考案しただけあって、友人関係などのグラフ状のデータを(画面に描画のために)取得する言語といえます。 8 | クエリの対象としてはグラフ構造(的な何か)ですが、レスポンスはツリー構造になります(@{query-and-data})。 9 | 10 | //image[query-and-data][クエリとレスポンスの構造を見比べる]{ 11 | //} 12 | 13 | == GraphQLを用いる目的 14 | 15 | GraphQLを用いる最大の目的は、クライアント側の設計とサーバ側の設計をよりよく分離することでしょう。 16 | 17 | RESTfulなAPIの場合、1つの画面を描画するために複数回エンドポイントを叩くか、その画面専用のエンドポイントを1回叩くかするのが一般的です。 18 | サーバ側設計をきれいに保つ代わりに何回ものリクエストが発生することを許容するか、オーバーヘッドをなくすためにサーバとクライアントが協力して複雑なことをするかのトレードオフが発生します。 19 | 20 | 一方、GraphQLでは1回のクエリで複数の値が取れたり、ネストした構造のデータも一度に取得することができます。 21 | しかも、そのサーバ側の実装はある画面のための特別な実装ではなく、他の画面でも使い回せる共通のものなのです。 22 | 性能を追求する上で、クライアントとサーバが密接に協力しなくてもよい、疎な結合にしておけるのは大変に魅力的です。 23 | 24 | == GraphQLって本当に使えるの? 25 | 26 | 筆者は使えると現在のところ考えています。 27 | とはいえ、RESTfulなAPIと比べて目立ってシェアを奪いはじめている感じでもないですが…。 28 | 筆者としては、今後新しくシステムを組む場合、まずはGraphQLファーストで取り組んでみようかと考える程度には気に入っています。 29 | 30 | もちろん、GraphQLは万能ではありませんので、RESFTfulなAPIを完全に廃止できるとは考えないほうがよいでしょう。 31 | たとえば、バイナリファイルについての扱いについてはまだエコシステムが育っていないのもあり、効率よく扱うには大変な苦労があるでしょう。 32 | よって、ファイルや画像のアップロードやダウンロードなどではGraphQLを使う価値は薄いでしょう。 33 | 34 | === クライアントから見た感想 35 | 36 | よくあるクライアント側の誤解をここで揉みほぐしておきます。 37 | 画面を分割して統治するため、我々は部品として切り出しこれをコンポーネントとしていると思います。 38 | 「でも、各コンポーネントが自分の欲しいデータを得るために勝手にクエリ投げたら結局重たいじゃん?」 39 | しかし、それは誤解です。 40 | 41 | GraphQLにはFragmentという仕様があります。 42 | 各コンポーネントは自分が欲しいデータをFragmentとして定義し、上流のコンポーネントに流します。 43 | 最上位のコンポーネントはこれを総括し、単一のクエリとしてサーバに投げ、帰ってきたデータを下流に分配します。 44 | この仕組により複数のコンポーネントがまさに自分の欲しいデータのみを要求し、かつ効率的に動作することができるのです。 45 | 画面をコンポーネントに分割して統治するのであれば、クエリも同様にFragmentに分割して統治するというわけです。 46 | 47 | この仕組みは本当に正しくて、クライアント側が実際使っているフィールドはどこなのか?という問題をうまく解決しています。 48 | Fragmentを使わずに効率的にやろうとした場合、結局クエリに下流のコンポーネントの要求をひたすらべた書きしなければなりません。 49 | すると、下流のどのコンポーネントがどの値を使っているかがわからくて下手にクエリを削れない!…ということになりかねません。 50 | 51 | === サーバから見た感想 52 | 53 | サーバから見た感想としては、満たすべき仕様がちょいちょい多くあってつらい場面もありますが、基本的には楽になると言ってよいでしょう。 54 | @{best-practice}で詳しく述べますが、RESTfulなAPIは実質@{RPC,Remote Procedure Call}です。 55 | 一方、GraphQLはその名の示すとおりQuery Languageです。 56 | この差はかなり大きく、RESTful APIを実装するサーバ側コードを流用しようとするとクライアント側から見て理解に苦しむ挙動になりがちです。 57 | 58 | また、アクセス権限を絞ったりするのにはDirectiveという仕様@{directive-spec}の存在が大きいです。 59 | 実際に自分でGraphQLサーバを構成してみると、GET系の処理についてはDB+Directiveで設計したルールベースの制御だけで賄える気すらしています。 60 | もしかしたら、prisma@{prisma}のようなGraphQL特化の@{ORM,Object Relation Mapping}というのは筋のよい考え方なのかもしれません。 61 | 自分は使わないと思いますが…。 62 | 63 | //footnote[directive-spec][@{https://facebook.github.io/graphql/draft/#sec-Language.Directives}] 64 | //footnote[prisma][@{https://www.prisma.io/}] 65 | 66 | == Introspectionがつよい 67 | 68 | Introspection(自己観察)が強いです。 69 | GraphQLは色々なデータに対してクエリを投げ、結果を取得することができます。 70 | この構造はメタ的なデータについても同様で、あるGraphQLサーバがどういうオペレーションを持ち、どういう型があり、それらがどういうフィールドをもつのかをクエリで調べることができます。 71 | いわゆるリフレクション的なことができるのです。 72 | 73 | この構造はかなり強く、多くのツールがこの機能をフルに活用しています。 74 | なんなら、関連仕様の記述もIntrospectionからの返り値をコピペして済ませている箇所すらあります。 75 | つよい! 76 | 77 | GraphQLはきっちりと型付けされていて、しかもその情報をサーバから引き出せるわけですのでWeb UIなども非常に洗練されていて使い心地がよいです。 78 | そしてクエリも投げる前にサーバが受付可能であるかを事前にチェックするため、多くの実行時エラーを減らすことに成功しています。 79 | 80 | == 一瞬で腑に落ちるGraphQLサーバの概念 81 | 82 | 筆者がGraphQLの勉強を始めた時に最初に知りたかったことを書いておきます。 83 | それは、GraphQLサーバの実装はResolverの集合体であるということです。 84 | 85 | 技術書典のデータ構造を例に考えてみます。 86 | まず最初にEvent(イベント)があり、その下にCircleExhibitInfo(サークル参加情報)があり、さらにその下にProductInfo(頒布物情報)があります(@{tbf-chain})。 87 | クエリにあわせ、まず最初にイベントをresolve(解決)し、それにぶら下がる数百のサークルをresolveし、各サークル毎に1〜n個の頒布物情報をresolveします。 88 | ここでそれぞれにResolverを定義します。 89 | 1. クエリ→イベントのResolver、2. イベント→サークルリストのResolver、3. サークル→頒布物リストのResolver という感じです。 90 | そして、これらすべてのResolverを統合したものがGraphQLサーバなのです。 91 | 92 | //image[tbf-chain][データ構造の実例]{ 93 | //} 94 | 95 | ResolverがNodeを生成し、Nodeがツリー構造を成し、これがレスポンスとなるのです。 96 | よって、GraphQLサーバとはどうやってResolverを定義するか、そしてそれを統合するか、という問題に還元されていきます。 97 | 98 | 順当に考えると、Facebookが抱えるような"友達の友達"という構造に対して全データを取得しようとするのは危険です。 99 | 友達の友達には循環構造が含まれるため、再帰的に処理し続けると無限ループになることは想像に難くありません。 100 | そこで、クライアントから求めたデータ構造にマッチするよう、クエリドリブンで動作するResolverが妥当なわけです。 101 | 102 | == GraphQLの勉強の仕方 103 | 104 | とりあえず@{https://www.howtographql.com/}を読むのがいいと思います。 105 | そして仕様@{graphl-spec}も読むとよいでしょう。 106 | GraphQLの仕様は転送方法レベルの話はないのでざっくり斜め読みでよいでしょう。 107 | 何ならAppendixのGrammar Summaryだけでもよいかもしれません。 108 | 109 | //footnote[graphl-spec][@{https://facebook.github.io/graphql/}] 110 | 111 | 市井の記事はクライアント側の話に偏っているため、サーバ側の知見はわりと自分で試して覚えろ的なところがあります。 112 | また、Node.jsやRubyでの事例が多いため、gqlgenについてはこれからといえそうです。 113 | やっていきましょう。 114 | @{https://gitter.im/gqlgen/Lobby}に公式のチャットルームがあるので活用するとよいでしょう。 115 | 116 | 筆者が今まで勉強してきた軌跡は@{http://b.hatena.ne.jp/vvakame/GraphQL}にあります。 117 | また、Mercari Tech Conf 2018のサイトでもgqlgenを使っています。 118 | このソースは@{https://github.com/mercari/mtc2018-web}で公開しているので、眺めてみるのもよいでしょう。 119 | 120 | //comment{ 121 | TODO 122 | https://github.com/mercari/mtc2018-web/pull/134 とか mtcで色々試したことについて言及する 123 | //} 124 | -------------------------------------------------------------------------------- /articles/layouts/layout.html.erb: -------------------------------------------------------------------------------- 1 | <%= @body %> 2 | -------------------------------------------------------------------------------- /articles/layouts/layout.tex.erb: -------------------------------------------------------------------------------- 1 | \documentclass[<%= @documentclassoption %>]{<%= @documentclass %>} 2 | <%- if @texcompiler == "uplatex" -%> 3 | \usepackage[deluxe,uplatex]{otf} 4 | <%- else -%> 5 | \usepackage[deluxe]{otf} 6 | <%- end -%> 7 | \usepackage[dvipdfmx]{xcolor} 8 | \usepackage[dvipdfmx]{graphicx} 9 | \usepackage{framed} 10 | \usepackage{wrapfig} 11 | \definecolor{shadecolor}{gray}{0.9} 12 | \definecolor{shadecolorb}{gray}{0.1} 13 | \definecolor{reviewgreen}{rgb}{0,0.4,0} 14 | \definecolor{reviewblue}{rgb}{0.2,0.2,0.4} 15 | \definecolor{reviewred}{rgb}{0.7,0,0} 16 | \definecolor{reviewdarkred}{rgb}{0.3,0,0} 17 | \usepackage{lmodern} 18 | \usepackage[T1]{fontenc} 19 | \usepackage{textcomp} 20 | \usepackage[utf8]{inputenc} 21 | \usepackage{ascmac} 22 | \usepackage{float} 23 | \usepackage{alltt} 24 | \usepackage{amsmath} 25 | 26 | 27 | %% if you use @{} (underline), use jumoline.sty 28 | %%\usepackage{jumoline} 29 | 30 | \newenvironment{shadedb}{% 31 | \def\FrameCommand{\fboxsep=\FrameSep \colorbox{shadecolorb}}% 32 | \MakeFramed {\FrameRestore}}% 33 | {\endMakeFramed} 34 | 35 | <%- unless ["utbook", "tbook"].include?(@documentclass) -%> 36 | %\usepackage[top=10zw,bottom=12zw,left=10zw,right=10zw]{geometry} 37 | %\usepackage[top=5zw,bottom=5zw,left=1zw,right=1zw]{geometry} 38 | <%- end -%> 39 | 40 | \newcommand{\parasep}{\vspace*{3zh}} 41 | \setlength{\footskip}{30pt} 42 | 43 | <%- if @texcompiler == "uplatex" -%> 44 | \usepackage[dvipdfmx,bookmarks=true,bookmarksnumbered=true,colorlinks=true,% 45 | pdftitle={<%= @config.name_of("booktitle") %>},% 46 | pdfauthor={<%= @config.names_of("aut").join(I18n.t("names_splitter")) %>}]{hyperref} 47 | % uplatexでのBookmarkの文字化け対策(日本語向け) 48 | \usepackage[dvipdfmx]{pxjahyper} 49 | <%- else -%> 50 | %% Bookmarkの文字化け対策(日本語向け) 51 | \ifnum 46273=\euc"B4C1 % 46273 == 0xB4C1 == 漢(EUC-JP) 52 | \usepackage{atbegshi}% 53 | \AtBeginShipoutFirst{\special{pdf:tounicode EUC-UCS2}}% 54 | %%\AtBeginDvi{\special{pdf:tounicode EUC-UCS2}}% 55 | \else 56 | \usepackage{atbegshi}% 57 | \AtBeginShipoutFirst{\special{pdf:tounicode 90ms-RKSJ-UCS2}}% 58 | %%\AtBeginDvi{\special{pdf:tounicode 90ms-RKSJ-UCS2}}% 59 | \fi 60 | 61 | \usepackage[dvipdfm,bookmarks=true,bookmarksnumbered=true,colorlinks=true,% 62 | pdftitle={<%= @config.name_of("booktitle") %>},% 63 | pdfauthor={<%= @config.names_of("aut").join(I18n.t("names_splitter")) %>}]{hyperref} 64 | <%- end -%> 65 | 66 | <%- if ["utbook", "tbook"].include?(@documentclass) -%> 67 | \newcommand{\headfont}{\gtfamily\sffamily\bfseries} 68 | \usepackage{plext} 69 | <%- end -%> 70 | 71 | <%- if config["highlight"] && config["highlight"]["latex"] == "listings" -%> 72 | <%- if config["language"] == "ja" -%> 73 | \usepackage{listings,jlisting} 74 | <%- else -%> 75 | \usepackage{listings} 76 | <%- end -%> 77 | \renewcommand{\lstlistingname}{<%= I18n.t("list")%>} 78 | \lstset{% 79 | breaklines=true,% 80 | breakautoindent=false,% 81 | breakindent=0pt,% 82 | fontadjust=true,% 83 | backgroundcolor=\color{shadecolor},% 84 | frame=single,% 85 | framerule=0pt,% 86 | basicstyle=\ttfamily\scriptsize,% 87 | commentstyle=\color{reviewgreen},% 88 | identifierstyle=\color{reviewblue},% 89 | stringstyle=\color{reviewred},% 90 | keywordstyle=\bfseries\color{reviewdarkred},% 91 | } 92 | 93 | \lstnewenvironment{reviewemlistlst}[1][]{\lstset{#1}}{} 94 | \lstnewenvironment{reviewemlistnumlst}[1][]{\lstset{numbers=left, #1}}{} 95 | \lstnewenvironment{reviewlistlst}[1][]{\lstset{#1}}{} 96 | \lstnewenvironment{reviewlistnumlst}[1][]{\lstset{numbers=left, #1}}{} 97 | \lstnewenvironment{reviewcmdlst}[1][]{\lstset{backgroundcolor=\color{white}, frameround=tttt, frame=trbl, #1}}{} 98 | <%- end -%> 99 | 100 | \newenvironment{reviewimage}{% 101 | \begin{figure}[H] 102 | \begin{center}}{% 103 | \end{center} 104 | \end{figure}} 105 | 106 | \newenvironment{reviewdummyimage}{% 107 | \begin{figure}[H] 108 | \begin{center}\begin{alltt}}{% 109 | \end{alltt}\end{center} 110 | \end{figure}} 111 | 112 | \newenvironment{reviewemlist}{% 113 | \medskip\small\begin{shaded}\setlength{\baselineskip}{1.3zw}\begin{alltt}}{% 114 | \end{alltt}\end{shaded}} 115 | 116 | \newenvironment{reviewlist}{% 117 | \begin{shaded}\small\setlength{\baselineskip}{1.3zw}\begin{alltt}}{% 118 | \end{alltt}\end{shaded}\par\vspace*{0.5zw}} 119 | 120 | \newenvironment{reviewcmd}{% 121 | \color{white}\medskip\small\begin{shadedb}\setlength{\baselineskip}{1.3zw}\begin{alltt}}{% 122 | \end{alltt}\end{shadedb}} 123 | 124 | \newenvironment{reviewbox}{% 125 | \medskip\small\begin{framed}\setlength{\baselineskip}{1.3zw}\begin{alltt}}{% 126 | \end{alltt}\end{framed}} 127 | 128 | \newenvironment{reviewtable}[1]{% 129 | \begin{center}\small\setlength{\baselineskip}{1.2zw} 130 | \begin{tabular}{#1}}{% 131 | \end{tabular} 132 | \end{center}} 133 | 134 | \newenvironment{reviewcolumn}{% 135 | \begin{framed} 136 | }{% 137 | \end{framed} 138 | \vspace{2zw}} 139 | 140 | \newcommand{\reviewcolumnhead}[2]{% 141 | {\noindent\large <%= I18n.t("column_head")%>: #2}} 142 | 143 | \newcommand{\reviewtablecaption}[1]{% 144 | \caption{#1}} 145 | 146 | \newcommand{\reviewimgtablecaption}[1]{% 147 | \caption{#1}\vspace{-3mm}} 148 | 149 | \newcommand{\reviewbackslash}[0]{% 150 | \textbackslash{}} 151 | 152 | \newcommand{\reviewlistcaption}[1]{% 153 | \medskip{\small\noindent #1}\vspace*{-1.3zw}} 154 | 155 | \newcommand{\reviewemlistcaption}[1]{% 156 | \medskip{\small\noindent #1}\vspace*{-1.3zw}} 157 | 158 | \newcommand{\reviewcmdcaption}[1]{% 159 | \medskip{\small\noindent #1}\vspace*{-1.3zw}} 160 | 161 | \newcommand{\reviewindepimagecaption}[1]{% 162 | \begin{center}#1\end{center}} 163 | 164 | \newcommand{\reviewboxcaption}[1]{% 165 | \medskip{\small\noindent #1}\vspace*{-1.3zw}} 166 | 167 | \newcommand{\reviewimageref}[2]{<%= I18n.t("image")%> #1} 168 | \newcommand{\reviewtableref}[2]{<%= I18n.t("table")%> #1} 169 | \newcommand{\reviewlistref}[1]{<%= I18n.t("list")%> #1} 170 | \newcommand{\reviewbibref}[2]{#1} 171 | \newcommand{\reviewcolumnref}[2]{<%= I18n.t("columnname")%> #1} 172 | \newcommand{\reviewsecref}[2]{#1} 173 | 174 | \newcommand{\reviewminicolumntitle}[1]{% 175 | {\large <%= I18n.t("memo_head")%>: #1}\\} 176 | 177 | <%- if @config["toctitle"].present? -%> 178 | \renewcommand{\contentsname}{<%= @config["toctitle"]%>} 179 | <%- end -%> 180 | 181 | \newenvironment{reviewminicolumn}{% 182 | \vspace{1.5zw}\begin{screen}}{% 183 | \end{screen}\vspace{2zw}} 184 | 185 | \newcommand{\reviewkw}[1]{\textbf{\textgt{#1}}} 186 | \newcommand{\reviewami}[1]{\mask{#1}{A}} 187 | \newcommand{\reviewem}[1]{\textbf{#1}} 188 | \newcommand{\reviewstrong}[1]{\textbf{#1}} 189 | \newcommand{\reviewunderline}{\Underline} 190 | 191 | %% @ is ignored in LaTeX with default style 192 | \newcommand{\reviewstrike}[1]{#1} 193 | 194 | %%%% for ulem.sty: 195 | %%\renewcommand{\reviewstrike}[1]{\sout{#1}} 196 | %% 197 | %%%% for jumoline.sty: 198 | %%\renewcommand{\reviewstrike}[1]{\Middleline{#1}} 199 | 200 | \newcommand{\reviewth}[1]{\textgt{#1}} 201 | \newcommand{\reviewtitlefont}[0]{\usefont{T1}{phv}{b}{n}\gtfamily} 202 | \newcommand{\reviewmainfont}[0]{} 203 | \newcommand{\reviewcolophon}[0]{\clearpage} 204 | \newcommand{\reviewappendix}[0]{\appendix} 205 | 206 | \makeatletter 207 | %% maxwidth is the original width if it is less than linewidth 208 | %% otherwise use linewidth (to make sure the graphics do not exceed the margin) 209 | \def\maxwidth{% 210 | \ifdim\Gin@nat@width>\linewidth 211 | \linewidth 212 | \else 213 | \Gin@nat@width 214 | \fi 215 | } 216 | \makeatother 217 | 218 | <%- if @config["usepackage"] -%> 219 | <%= @config["usepackage"] %> 220 | <%- end -%> 221 | 222 | \usepackage[T1]{fontenc} 223 | 224 | \begin{document} 225 | 226 | \reviewmainfont 227 | 228 | \frontmatter 229 | \pagenumbering{arabic} 230 | 231 | <%- if @config["titlepage"] -%> 232 | <%- if @custom_titlepage -%> 233 | <%= @custom_titlepage %> 234 | <%- else -%> 235 | \begin{titlepage} 236 | <%- if @config["coverimage"] -%> 237 | \begin{center} 238 | \includegraphics[<%= @coverimageoption%>]{./images/<%= @config["coverimage"] %>} 239 | \end{center} 240 | \clearpage 241 | <%- end -%> 242 | \thispagestyle{plainhead} 243 | \setcounter{page}{1} 244 | \begin{center}% 245 | \mbox{} \vskip5zw 246 | \reviewtitlefont% 247 | {\Huge <%= @config.name_of("booktitle") %> \par}% 248 | \vskip 15em% 249 | {\huge 250 | \lineskip .75em 251 | \begin{tabular}[t]{c}% 252 | <%= @authors %> 253 | \end{tabular}\par}% 254 | \vfill 255 | {\large <%= @config["date"] %> <%= I18n.t("edition")%>\hspace{2zw}<%= I18n.t("published_by", @config.names_of("pbl").join(I18n.t("names_splitter")))%>\par}% 256 | \vskip4zw\mbox{} 257 | \end{center}% 258 | \end{titlepage} 259 | <%- end -%> 260 | <%- end -%> 261 | 262 | %%% originaltitle 263 | <%- if @config["originaltitlefile"] -%> 264 | <%= @custom_originaltitlepage %> 265 | <%- end -%> 266 | 267 | %%% credit 268 | <%- if @config["creditfile"] -%> 269 | <%= @custom_creditpage %> 270 | <%- end -%> 271 | 272 | %% preface 273 | <%= @input_files["PREDEF"] %> 274 | 275 | <%- if @config["toc"] -%> 276 | \setcounter{tocdepth}{<%= @config["toclevel"] %>} 277 | \tableofcontents 278 | <%- end -%> 279 | 280 | \begingroup 281 | \cleardoublepage 282 | \edef\continuenumber{\endgroup 283 | \noexpand\mainmatter 284 | \setcounter{page}{\the\value{page}}% 285 | } 286 | 287 | \mainmatter 288 | \continuenumber 289 | 290 | \renewcommand{\chaptermark}[1]{\markboth{\prechaptername\thechapter\postchaptername~#1}{}} 291 | <%= @input_files["CHAPS"] %> 292 | \renewcommand{\chaptermark}[1]{\markboth{\appendixname\thechapter~#1}{}} 293 | \reviewappendix 294 | <%= @input_files["APPENDIX"] %> 295 | 296 | %% backmatter begins 297 | <%- if @input_files["POSTDEF"] or @config["colophon"] -%> 298 | \backmatter 299 | <%- end -%> 300 | 301 | <%- if @input_files["POSTDEF"] -%> 302 | <%= @input_files["POSTDEF"] %> 303 | <%- end -%> 304 | 305 | %%% profile 306 | <%- if @config["profile"] -%> 307 | <%= @custom_profilepage %> 308 | <%- end -%> 309 | 310 | %%% advfile 311 | <%- if @config["advfile"] -%> 312 | <%= @custom_advfilepage %> 313 | <%- end -%> 314 | 315 | %%% colophon 316 | <%- if @config["colophon"] -%> 317 | <%- if @custom_colophonpage -%> 318 | <%= @custom_colophonpage %> 319 | <%- else -%> 320 | %% okuduke 321 | \reviewcolophon 322 | \thispagestyle{plainhead} 323 | 324 | \vspace*{\fill} 325 | 326 | {\noindent\reviewtitlefont\Large <%= @config.name_of("booktitle") %>} \\ 327 | \rule[8pt]{14cm}{1pt} \\ 328 | {\noindent 329 | <%= @config["pubhistory"].to_s.gsub(/\n/){"\n\n\\noindent\n"} %> 330 | } 331 | 332 | \begin{tabular}{ll} 333 | <%= @okuduke %> 334 | \end{tabular} 335 |  \\ 336 | \rule[0pt]{14cm}{1pt} \\ 337 | <%= @config.names_of("rights").join('\\' + '\\') %> \\ 338 | <%- end -%> 339 | <%- end -%> 340 | 341 | %%% backcover 342 | <%- if @config["backcover"] -%> 343 | <%= @custom_backcoverpage %> 344 | <%- end -%> 345 | 346 | \end{document} 347 | -------------------------------------------------------------------------------- /articles/locale.yml: -------------------------------------------------------------------------------- 1 | locale: ja 2 | column_head: ■コラム 3 | columnname: コラム 4 | memo_head: ■メモ 5 | image: 図 6 | table: 表 7 | list: リスト 8 | -------------------------------------------------------------------------------- /articles/preface.re: -------------------------------------------------------------------------------- 1 | = はじめに 2 | 3 | 本書はGraphQLサーバをGo言語で構築していく本です。 4 | と思いきやベストプラクティスを書ききったところで割と力尽きたのでツール自体の使い方の話は少ないです。 5 | 後日、@{https://github.com/vvakame/graphql-with-go-book}にて本書の全文を無料公開します。 6 | 7 | 主に話題にするツールは筆者もコミッタになっているgqlgen@{gqlgen}です。 8 | 本書執筆時点でのgqlgenのバージョンは次のとおりです。 9 | 10 | //footnote[gqlgen][@{https://github.com/99designs/gqlgen}] 11 | 12 | //cmd{ 13 | #@# docker に go 入れるのだるいので #@mapoutput(cd ../code && go list -m -u github.com/99designs/gqlgen) 14 | github.com/99designs/gqlgen v0.5.1 15 | #@# #@end 16 | //} 17 | 18 | 残念ながら、筆者はまだGraphQLを使ったなんらかのAPIサーバをリリースできていません。 19 | よって、ここで語られる知見は今後アップデートされ、変わっていく可能性があります。 20 | 21 | もしあなたが次の範囲の人であれば、ぜひ本書を読み進めていっていただきたいです。 22 | 23 | * GraphQLに興味がある 24 | ** 特にサーバ側視点の話が知りたい 25 | ** 罠とかも知りたい 26 | * Go言語でやっていきたい 27 | * schemaありきで開発を進めたい 28 | * ボイラープレートなコードは嫌いです 29 | 30 | 逆に、次のような話題については深く言及しません。 31 | 32 | * GraphQLの仕様自体に対しての懇切丁寧な説明 33 | * 特定のクライアント側ツールに関する話 34 | * クライアント側についての設計のベストプラクティス@{mtc-front} 35 | 36 | //footnote[mtc-front][ちょっとだけあるからここ見て→@{https://github.com/mercari/mtc2018-web/pull/134}] 37 | 38 | //comment{ 39 | * TODO 某wikiに書いた内容をもう一回読み返して盛り込む 40 | //} 41 | -------------------------------------------------------------------------------- /articles/prh.yml: -------------------------------------------------------------------------------- 1 | # https://gist.github.com/inao/f55e8232e150aee918b9 2 | # from WEB+DB PRESS 3 | # 対応は不完全(途中で飽きた) 一部手心を加えている 4 | version: 1 5 | 6 | imports: 7 | - ../prh-rules/media/techbooster.yml 8 | 9 | rules: 10 | - expected: GraphQL 11 | pattern: /GrapQL/i 12 | -------------------------------------------------------------------------------- /articles/sty/bxglyphwiki.sty: -------------------------------------------------------------------------------- 1 | % bxglyphwiki.sty 2 | 3 | %% package declaration 4 | \NeedsTeXFormat{LaTeX2e} 5 | \ProvidesPackage{bxglyphwiki}[2017/05/07 v0.4a] 6 | \def\bxgw@pkgname{bxglyphwiki} 7 | 8 | %% switches 9 | \newif\ifbxgw@ok 10 | \newif\ifbxgw@unicode 11 | \newif\ifbxgw@ptex 12 | \newif\ifbxgw@use@bb 13 | \newif\ifbxgw@internallua 14 | 15 | %% packages 16 | \RequirePackage{pdftexcmds}[2009/09/23]% v0.6 17 | \RequirePackage{ifluatex,ifxetex,ifpdf} 18 | 19 | %--------------------------------------- environment check 20 | 21 | %% engine check 22 | \pdf@isprimitive\kanjiskip\kanjiskip{\bxgw@ptextrue}{} 23 | \bxgw@oktrue 24 | \ifluatex 25 | \bxgw@unicodetrue 26 | \bxgw@internalluatrue 27 | \else\ifxetex 28 | \bxgw@unicodetrue 29 | \else\ifbxgw@ptex 30 | \ifx\ucs\@undefined\else\ifnum\ucs"3000="3000 31 | \bxgw@unicodetrue \fi\fi 32 | \else 33 | \PackageError\bxgw@pkgname 34 | {The engine in use is not supported}\@ehc 35 | \bxgw@okfalse 36 | \fi\fi\fi 37 | \ifbxgw@ok\else\expandafter\endinput\fi\relax 38 | 39 | %% \bxgw@ifvertdir@ptex 40 | \@onlypreamble\bxgw@ifvertdir@ptex 41 | \def\bxgw@ifvertdir@ptex{\bxgw@cond{% 42 | \if\iftdir\ifmdir F\else T\fi\else F\fi T% 43 | }\fi} 44 | 45 | %--------------------------------------- configuration interface 46 | % The commands here will be used in the config file. 47 | 48 | %% packages 49 | \RequirePackage{keyval} 50 | 51 | %% unique tokens 52 | \def\bxgw@mark{\bxgw@mark@} 53 | \def\bxgw@end{\bxgw@end@} 54 | \let\bxgw@stop\relax 55 | 56 | %%<*> \setgwworkdir{} 57 | % Specifies the directory where the files created by this 58 | % package will be located. 59 | \newcommand*\setgwworkdir[1]{% 60 | \edef\bxgw@wdir{#1}% 61 | % strip a traling / 62 | \expandafter\bxgw@setgwworkdir@a\bxgw@wdir 63 | \bxgw@mark/\bxgw@mark\bxgw@end 64 | } 65 | \def\bxgw@setgwworkdir@a#1/\bxgw@mark#2\bxgw@end{% 66 | \ifx\bxgw@end#2\bxgw@end\else \def\bxgw@wdir{#1}\fi 67 | \ifx\bxgw@wdir\@empty \def\bxgw@wdir{.}\fi 68 | } 69 | 70 | %%<*> \setgwimageformat{} 71 | % The image file format to be used; possible 72 | % values are 'eps' and 'pdf'. 73 | \newcommand*\setgwimageformat[1]{% 74 | \edef\bxgw@img@fmt{#1}% 75 | } 76 | 77 | %%<*> \setgwcommand{} 78 | % The name of the helper command. 79 | \newcommand*\setgwcommand[1]{% 80 | \edef\bxgw@command{#1}% 81 | } 82 | 83 | %%<*> \setgwcache{} 84 | % Sets the cache duration in minutes, or 'forever'. 85 | \newcommand*\setgwcache[1]{% 86 | \edef\bxgw@tmpa{#1}% 87 | \def\bxgw@tmpb{never}\ifx\bxgw@tmpa\bxgw@tmpb 88 | \def\bxgw@tmpa{-60}\fi 89 | \def\bxgw@tmpb{forever}\ifx\bxgw@tmpa\bxgw@tmpb 90 | \def\bxgw@tmpa{\maxdimen}\fi 91 | \afterassignment\bxgw@setgwcache@a\@tempcnta\bxgw@tmpa\bxgw@stop 92 | } 93 | \def\bxgw@setgwcache@a#1\bxgw@stop{% 94 | \ifx\bxgw@end#1\bxgw@end 95 | \edef\bxgw@cache{\the\@tempcnta}% 96 | \else 97 | \PackageError\bxgw@pkgname 98 | {Illegal number format}\@eha 99 | \fi 100 | } 101 | 102 | %--------------------------------------- option handling 103 | 104 | %% strings 105 | \def\bxgw@hyph{-} 106 | 107 | %% driver options 108 | \let\bxgw@drv\bxgw@hyph 109 | \def\bxgw@drv@opt#1{% 110 | \edef\bxgw@drv{\CurrentOption}% 111 | \def\bxgw@img@fmt{#1}% 112 | } 113 | \DeclareOption{dvips} {\bxgw@drv@opt{eps}} 114 | \DeclareOption{dvipdfmx}{\bxgw@drv@opt{pdf}} 115 | \DeclareOption{pdftex} {\bxgw@drv@opt{pdf}} 116 | \DeclareOption{luatex} {\bxgw@drv@opt{pdf}} 117 | \DeclareOption{xetex} {\bxgw@drv@opt{pdf}} 118 | %% download options 119 | \chardef\bxgw@download\@ne 120 | \DeclareOption{download}{\chardef\bxgw@download\@ne} 121 | \DeclareOption{nodownload}{\chardef\bxgw@download\z@} 122 | %\DeclareOption{forcedownload}{\chardef\bxgw@download\tw@} 123 | \DeclareOption{internallua}{\bxgw@internalluatrue} 124 | \DeclareOption{nointernallua}{\bxgw@internalluafalse} 125 | %% handling keyval options 126 | \DeclareOption*{% 127 | \def\bxgw@next{\setkeys{bxgw}}% 128 | \expandafter\bxgw@next\expandafter{\CurrentOption}% 129 | } 130 | %% dir option 131 | \define@key{bxgw}{dir}{% 132 | \g@addto@macro\bxgw@opt@post{\setgwworkdir{#1}}% 133 | } 134 | %% cache options 135 | \define@key{bxgw}{cache}{% 136 | \g@addto@macro\bxgw@opt@post{\setgwcache{#1}}% 137 | } 138 | 139 | %% default configurations 140 | \setgwworkdir{.} 141 | \setgwcache{10080}% one week 142 | \setgwimageformat{eps} 143 | \IfFileExists{./\bxgw@pkgname.lua} 144 | {\setgwcommand{texlua \bxgw@pkgname.lua}} 145 | {\setgwcommand{\bxgw@pkgname}} 146 | \def\bxgw@pfx{bxgw} 147 | 148 | %% read the config file 149 | \IfFileExists{\bxgw@pkgname.cfg}{% 150 | \makeatother 151 | \input{\bxgw@pkgname.cfg}% 152 | \makeatletter 153 | }{} 154 | 155 | %% driver adjustment 156 | \ifxetex 157 | \ExecuteOptions{xetex}% 158 | \else\ifluatex 159 | \ifpdf 160 | \IfFileExists{luatex.def}{% 161 | \ExecuteOptions{luatex}% 162 | }{% 163 | \ExecuteOptions{pdftex}% 164 | }% 165 | \fi 166 | \fi\fi 167 | \@ifpackageloaded{graphics}{% 168 | \def\bxgw@next#1.def#2\bxgw@end{% 169 | \ExecuteOptions{#1}}% 170 | \expandafter\bxgw@next\Gin@driver\bxgw@end 171 | }{} 172 | 173 | %% do it 174 | \let\bxgw@opt@post\@empty 175 | \ProcessOptions* 176 | \bxgw@opt@post 177 | 178 | %% driver adjustment (afterwards) 179 | \@ifpackageloaded{graphics}{% 180 | \RequirePackage{graphicx}% 181 | }{%else 182 | \ifx\bxgw@drv\bxgw@hyph\else 183 | \PackageInfo\bxgw@pkgname 184 | {Auto-load graphicx with driver '\bxgw@drv'}% 185 | \edef\bxgw@next{[\bxgw@drv]{graphicx}}% 186 | \expandafter\RequirePackage\bxgw@next 187 | \fi 188 | \AtBeginDocument{% 189 | \ifx\includegraphics\@undefined 190 | \PackageError\bxgw@pkgname 191 | {The graphics package is not auto-loaded because\MessageBreak 192 | the driver is unknown, and the package is not\MessageBreak 193 | manually loaded either}% 194 | {\@ehc}% 195 | \fi}% 196 | } 197 | 198 | %--------------------------------------- helpers 199 | 200 | %% variables 201 | \newdimen\bxgw@zw % zenkaku width 202 | \newwrite\bxgw@out 203 | 204 | %% strings 205 | \def\bxgw@@ast{*} 206 | \def\bxgw@@plus{+} 207 | \def\bxgw@@hyph{-} 208 | \def\bxgw@zero{0} 209 | 210 | %% \bxgw@cond\ifXX...\fi{}{} 211 | \@gobbletwo\if\if % if-match hack 212 | \def\bxgw@cond#1\fi{% 213 | #1\expandafter\@firstoftwo 214 | \else\expandafter\@secondoftwo\fi 215 | } 216 | 217 | %% \bxgw@unless\ifXX...\fi{} 218 | \@gobbletwo\if\if % if-match hack 219 | \def\bxgw@unless#1\fi{% 220 | #1\expandafter\@gobble 221 | \else\expandafter\@firstofone\fi 222 | } 223 | 224 | %% \bxgw@zs : zenkaku space 225 | %% \bxgw@geta : geta mark 226 | \ifbxgw@unicode 227 | \chardef\bxgw@zs="3000\relax 228 | \chardef\bxgw@geta="3013\relax 229 | \else 230 | \chardef\bxgw@zs=\jis"2121\relax 231 | \chardef\bxgw@geta=\jis"222E\relax 232 | \fi 233 | 234 | %% \bxgw@get@zw 235 | % Sets the right value of \bxgw@zw. 236 | \def\bxgw@get@zw{% 237 | \settowidth\bxgw@zw{\bxgw@zs}% 238 | } 239 | 240 | %% \bxgw@set@real@param 241 | \def\bxgw@set@real@param#1#2{% 242 | \def\bxgw@next{\edef#1{\strip@pt\bxgw@zw}}% 243 | \afterassignment\bxgw@srp@a\bxgw@zw=#2\p@\bxgw@stop 244 | } 245 | \def\bxgw@srp@a#1\bxgw@stop{% 246 | \ifx\bxgw@end#1\bxgw@end 247 | \bxgw@next 248 | \else 249 | \PackageError\bxgw@pkgname 250 | {Illegal number format}\@eha 251 | \fi 252 | } 253 | 254 | %% \bxgw@repeatable@hook 255 | \@onlypreamble\bxgw@repeatable@hook 256 | \let\bxgw@repeatable@hook\@empty 257 | \AtBeginDocument{\bxgw@repeatable@hook} 258 | \AtEndOfPackage{\bxgw@repeatable@hook} 259 | 260 | %% \bxgw@ifvertdir{}{} 261 | % Tests if the current writing direction is vertical. 262 | \ifbxgw@ptex 263 | \let\bxgw@ifvertdir\bxgw@ifvertdir@ptex 264 | \else 265 | \let\bxgw@ifvertdir\@secondoftwo % always false 266 | \fi 267 | \g@addto@macro\bxgw@repeatable@hook{% 268 | \ifx\ltj@curtfnt\@undefined\else 269 | \chardef\bxgw@luatexja@tate=3 270 | \def\bxgw@ifvertdir{\bxgw@cond 271 | \ifnum\ltjgetparameter{direction}=\bxgw@luatexja@tate\fi}% 272 | \fi 273 | } 274 | 275 | %% \bxgw@nunc : current timestamp 276 | %% \bxgw@tunc : cache limit timestamp 277 | \begingroup 278 | % Timestamp is the number of minutes elapsed from the epoch 279 | % of the Modified Julian Day. 280 | \@tempcnta=\year \@tempcntb=\month 281 | \ifnum\@tempcntb<3 282 | \advance\@tempcntb12 \advance\@tempcnta-1 283 | \fi 284 | \mathchardef\@tempyear\@tempcnta 285 | \chardef\@tempmonth\@tempcntb 286 | \multiply\@tempcnta1461 287 | \divide\@tempcnta4 \@tempcntb\@tempyear 288 | \divide\@tempcntb100 \advance\@tempcnta-\@tempcntb 289 | \divide\@tempcntb4 \advance\@tempcnta\@tempcntb 290 | \@tempcntb\@tempmonth \advance\@tempcntb-2 291 | \multiply\@tempcntb520 \divide\@tempcntb17 292 | \advance\@tempcnta\@tempcntb \advance\@tempcnta\day 293 | \advance\@tempcnta-678912 294 | \multiply\@tempcnta1440 \advance\@tempcnta\time 295 | \xdef\bxgw@nunc{\the\@tempcnta}% 296 | \advance\@tempcnta-\bxgw@cache 297 | \ifnum\@tempcnta<\z@ \@tempcnta\z@ \fi 298 | \xdef\bxgw@tunc{\the\@tempcnta}% 299 | \PackageInfo\bxgw@pkgname 300 | {current time = \bxgw@nunc\@gobble}% 301 | \endgroup 302 | 303 | %--------------------------------------- internallua availability 304 | 305 | %% check if 'internallua' is available 306 | \ifbxgw@internallua 307 | \ifluatex 308 | \chardef\bxgw@tmpa=\directlua{ 309 | tex.write(pcall(function() 310 | local chunk = assert(loadfile(assert( 311 | kpse.find_file("\bxgw@pkgname.lua", "lua", true)))) 312 | bxglyphwiki = {} 313 | chunk() 314 | end) and "1" or "0") 315 | } 316 | \ifnum\bxgw@tmpa>\z@ 317 | \PackageInfo\bxgw@pkgname 318 | {Entered 'internallua' mode\@gobble} 319 | \else 320 | \PackageWarning\bxgw@pkgname 321 | {Set-up for 'internallua' mode failed,\MessageBreak 322 | falling back to 'nointernallua' mode\@gobble} 323 | \bxgw@internalluafalse 324 | \fi 325 | \else 326 | \PackageWarning\bxgw@pkgname 327 | {You can use 'internallua' only on LuaTeX,\MessageBreak 328 | falling back to 'nointernallua' mode\@gobble} 329 | \bxgw@internalluafalse 330 | \fi 331 | \fi 332 | 333 | %--------------------------------------- glyph output 334 | 335 | %% \bxgw@scl : glyph scale 336 | \def\bxgw@scl{1.05} 337 | %% \bxgw@yadj : vertical adjustment (yoko) 338 | \def\bxgw@yadj{0} 339 | %% \bxgw@tadj : vertical adjustment (tate) 340 | \def\bxgw@tadj{0} 341 | %% \bxgw@bb : bounding box 342 | \def\bxgw@bb{0 0 1024 1024} 343 | 344 | %% set \ifbxgw@use@bb 345 | % Bbox is used only by dvipdfmx driver. 346 | \bxgw@use@bbfalse 347 | \def\bxgw@tmpa{dvipdfmx}\ifx\bxgw@tmpa\bxgw@drv 348 | \bxgw@use@bbtrue 349 | \fi 350 | 351 | %% \bxgw@ytzw 352 | \def\bxgw@ytzw#1#2{% 353 | \bxgw@ifvertdir{#2}{#1}\bxgw@zw 354 | } 355 | 356 | %% \bxgw@cht / \bxgw@cdp 357 | \def\bxgw@cht{\bxgw@htdp\cht{0.88}{0.5}} 358 | \def\bxgw@cdp{\bxgw@htdp\cdp{0.12}{0.5}} 359 | \def\bxgw@htdp#1#2#3{% 360 | \ifx#1\@undefined 361 | \ifbxgw@ytzw{#2}{#3}% 362 | \else #1% 363 | \fi 364 | } 365 | 366 | %% \bxgw@use@img{} 367 | % Places a glyph image as if it were a kanji. 368 | \def\bxgw@use@img#1{% 369 | \def\bxgw@img@file{#1}% 370 | \@tempdima\bxgw@ytzw{\bxgw@yadj}{\bxgw@tadj}% 371 | \advance\@tempdima\bxgw@ytzw{-0.12}{-0.5}% 372 | \ifx\adjustbaseline\@undefined\else 373 | \adjustbaseline 374 | \fi 375 | \edef\bxgw@vertadj{\the\@tempdima}% 376 | \edef\bxgw@next{\bxgw@img@args}% 377 | \bxgw@next 378 | } 379 | \def\bxgw@img@args{% 380 | \noexpand\raisebox{\bxgw@vertadj}[\bxgw@cht][\bxgw@cdp]% 381 | {\noexpand\makebox[\bxgw@zw][c]{% 382 | \bxgw@ifvertdir{\noexpand\rotatebox[origin=c]{90}}{}{% 383 | \noexpand\includegraphics 384 | [width=\bxgw@scl\bxgw@zw 385 | ,height=\bxgw@scl\bxgw@zw 386 | \ifbxgw@use@bb ,bb=\bxgw@bb \fi ]% 387 | {\bxgw@img@file}% 388 | }}}% 389 | } 390 | 391 | %% \bxgw@errorout 392 | \def\bxgw@errorout{% 393 | \bxgw@fallback\bxgw@geta 394 | } 395 | 396 | %% \bxgw@fallback{} 397 | % Prints a character as fallback; it is rendered with double 398 | % thick underlines beneath. 399 | \def\bxgw@fallback#1{% 400 | \ifx\adjustbaseline\@undefined\else 401 | \adjustbaseline 402 | \fi 403 | \raisebox{\z@}[\bxgw@cht][\bxgw@cdp]{% 404 | \makebox[\bxgw@zw]{#1}\kern-\bxgw@zw 405 | \rule[\bxgw@ytzw{-.15}{-.53}]{\bxgw@zw}{.05\bxgw@zw}% 406 | \kern-\bxgw@zw 407 | \rule[\bxgw@ytzw{-.25}{-.63}]{\bxgw@zw}{.05\bxgw@zw}}% 408 | } 409 | 410 | %--------------------------------------- user command 411 | 412 | %% variables 413 | \let\bxgw@glyph\@empty % glyph name 414 | \let\bxgw@rev\@empty % version spec (number macro) 415 | \let\bxgw@mode\relax % mode spec (pseudovar) 416 | 417 | %%<*> \setgwscale{} 418 | \newcommand*\setgwscale[1]{% 419 | \bxgw@set@real@param\bxgw@scl{#1}% 420 | } 421 | 422 | %%<*> \setgwyvertadjust{} 423 | \newcommand*\setgwyvertadjust[1]{% 424 | \bxgw@set@real@param\bxgw@yadj{#1}% 425 | } 426 | 427 | %%<*> \setgwtvertadjust{} 428 | \newcommand*\setgwtvertadjust[1]{% 429 | \bxgw@set@real@param\bxgw@tadj{#1}% 430 | } 431 | 432 | %%<*> \GWI[*][]{} 433 | % Prints a glyph! 434 | \@ifdefinable{\GWI}{% 435 | \DeclareRobustCommand*\GWI{% 436 | \bxgw@zs 437 | \bxgw@get@zw \kern-\bxgw@zw 438 | \begingroup 439 | \bxgw@input 440 | } 441 | } 442 | \def\bxgw@input{% 443 | \@ifstar{\bxgw@input@a*}{\bxgw@input@a+}% 444 | } 445 | \def\bxgw@input@a#1{% 446 | \@ifnextchar[% 447 | {\bxgw@input@b#1}{\bxgw@input@b#1[]}% 448 | } 449 | \def\bxgw@input@b#1[#2]#3{% 450 | \edef\bxgw@rev{#2}% 451 | \edef\bxgw@glyph{#3}% 452 | \ifx\bxgw@rev\@empty \def\bxgw@rev{#1}\fi 453 | \afterassignment\bxgw@parse@arg\@tempcnta0\bxgw@rev\relax 454 | \bxgw@validate@var\bxgw@glyph 455 | \bxgw@unless\ifbxgw@ok\fi{\bxgw@abort}% 456 | \bxgw@main 457 | \bxgw@exit 458 | } 459 | 460 | %% \bxgw@exit 461 | \def\bxgw@exit{% 462 | \endgroup 463 | \kern-\bxgw@zw \bxgw@zs 464 | } 465 | 466 | %% \bxgw@abort 467 | \def\bxgw@abort#1\bxgw@exit{% 468 | \bxgw@errorout 469 | \bxgw@exit 470 | } 471 | 472 | %% \bxgw@annihilate 473 | \def\bxgw@annihilate{% 474 | \let\bxgw@annihilated=t% 475 | \let\bxgw@main\bxgw@abort 476 | \chardef\bxgw@download\z@ 477 | } 478 | 479 | %% bxgw@parse@arg 480 | \def\bxgw@parse@arg#1\relax{% 481 | \def\bxgw@tmpa{#1}% 482 | \let\bxgw@mode\relax 483 | \ifx\bxgw@tmpa\@empty \chardef\bxgw@mode\z@ \fi 484 | \ifx\bxgw@tmpa\bxgw@@ast \chardef\bxgw@mode\@ne \fi 485 | \ifx\bxgw@tmpa\bxgw@@plus \chardef\bxgw@mode\tw@ \fi 486 | \bxgw@cond\ifx\bxgw@mode\relax\fi{\bxgw@abort}% 487 | \bxgw@cond{\ifnum\ifnum\bxgw@mode>\z@ \z@\else\@ne\fi>\@tempcnta}% 488 | \fi{\bxgw@abort}% 489 | \ifnum\@tempcnta=\z@ \@tempcnta=\@ne \fi 490 | \edef\bxgw@rev{\the\@tempcnta}% 491 | } 492 | 493 | %--------------------------------------- main procedure 494 | 495 | %% variables 496 | \let\bxgw@latest\@empty % latest version (number) 497 | \let\bxgw@stor\@empty % storage info 498 | \let\bxgw@rel\@empty % related character (text) 499 | \let\bxgw@rrev\@empty % resolved version (number) 500 | 501 | %% bxgw@rel/* 502 | 503 | %% \bxgw@resp@file 504 | \def\bxgw@resp@file{% 505 | \bxgw@wdir/\bxgw@pfx_resp_.def} 506 | %% \bxgw@bbox@file 507 | \def\bxgw@bbox@file{% 508 | \bxgw@wdir/\bxgw@pfx_bbox_.def} 509 | %% \bxgw@info@file 510 | \def\bxgw@info@file{% 511 | \bxgw@wdir/\bxgw@pfx_\bxgw@glyph.def} 512 | %% \bxgw@glyph@file 513 | \def\bxgw@glyph@file{% 514 | \bxgw@wdir/\bxgw@pfx_\bxgw@glyph_\bxgw@rev.\bxgw@img@fmt} 515 | 516 | 517 | %% \bxgw@main 518 | % The main procedure. 519 | \def\bxgw@main{% 520 | \bxgw@get@info 521 | \bxgw@resolve 522 | \let\bxgw@rev\bxgw@rrev 523 | \bxgw@put@glyph 524 | } 525 | 526 | %% \bxgw@get@info 527 | \def\bxgw@get@info{% 528 | \expandafter\let\expandafter\bxgw@rel\csname 529 | bxgw@rel/\bxgw@glyph\endcsname 530 | \bxgw@cond\ifx\bxgw@rel\relax\fi{% 531 | \bxgw@cond\ifnum\bxgw@download>\z@\fi{% 532 | \bxgw@exec@info 533 | \bxgw@cond\ifx\bxgw@resp\@empty\fi{\bxgw@abort}{}% 534 | \expandafter\bxgw@get@info@a\bxgw@resp 535 | }{\let\bxgw@rel\bxgw@geta}% 536 | }{}% 537 | \bxgw@read@info 538 | \global\expandafter\let\csname 539 | bxgw@rel/\bxgw@glyph\endcsname\bxgw@rel 540 | \ifnum\bxgw@rev>\bxgw@latest\relax 541 | \PackageWarning\bxgw@pkgname 542 | {The requested version (\bxgw@rev) is not yet present% 543 | \MessageBreak ('\bxgw@glyph', latest=\bxgw@latest),}% 544 | \let\bxgw@rev\bxgw@latest 545 | \fi 546 | } 547 | \def\bxgw@get@info@a#1#2{% 548 | \ifx#1#1% #1 is a single kanji token 549 | \def\bxgw@rel{#1}% 550 | \else \let\bxgw@rel\bxgw@geta 551 | \fi 552 | } 553 | 554 | %% \bxgw@read@info 555 | % Reads in the info file. 556 | % It stores data into \bxgw@latest and \bxgw@stor. 557 | \def\bxgw@read@info{% 558 | \let\bxgw@latest\relax \let\bxgw@stor\relax 559 | \let\do\bxgw@read@info@a 560 | \InputIfFileExists{\bxgw@info@file}{}{}% 561 | \bxgw@cond\ifx\bxgw@latest\relax\fi{% 562 | \PackageError\bxgw@pkgname 563 | {Glyph '\bxgw@glyph' is not available}\@ehd 564 | \bxgw@abort 565 | }{}% 566 | } 567 | \def\bxgw@read@info@a#1#2{% 568 | \def\bxgw@latest{#1}\def\bxgw@stor{#2}% 569 | } 570 | 571 | %% \bxgw@resolve 572 | % Deicdes the glyph version to use. 573 | \def\bxgw@resolve{% 574 | \let\bxgw@org@rev\rev 575 | \ifnum\bxgw@mode=\z@ \let\rev\bxgw@resolve@sing 576 | \else \let\rev\bxgw@resolve@mult 577 | \fi 578 | \bxgw@okfalse \let\bxgw@rrev\bxgw@zero 579 | \bxgw@stor 580 | \ifnum\bxgw@download=\z@ \bxgw@oktrue \fi 581 | \ifcase\bxgw@mode 582 | \let\bxgw@rrev\bxgw@rev 583 | \or % * 584 | \ifnum\bxgw@rrev=\z@ \let\bxgw@rrev\bxgw@latest \fi 585 | \or % + 586 | \ifbxgw@ok\else \let\bxgw@rrev\bxgw@latest \fi 587 | \fi 588 | \PackageInfo\bxgw@pkgname 589 | {Using ver.\bxgw@rrev\space for '\bxgw@glyph' 590 | (latest=\bxgw@latest),\MessageBreak 591 | \ifbxgw@ok without\else with\fi\space download,}% 592 | \let\rev\bxgw@org@rev 593 | } 594 | \def\bxgw@resolve@sing#1#2{% 595 | \ifnum\bxgw@rev=#1\relax \ifnum#2<\bxgw@tunc\relax\else 596 | \bxgw@oktrue 597 | \fi\fi 598 | } 599 | \def\bxgw@resolve@mult#1#2{% 600 | \ifnum#1<\bxgw@rev\else 601 | \ifnum#2<\bxgw@tunc\relax\else \bxgw@oktrue \fi 602 | \ifnum\bxgw@rrev<#1\relax \def\bxgw@rrev{#1}\fi 603 | \fi 604 | } 605 | 606 | %% \bxgw@put@glyph 607 | \def\bxgw@put@glyph{% 608 | \bxgw@cond\ifx\includegraphics\@undefined\fi{\bxgw@abort}{}% 609 | \bxgw@cond\ifbxgw@ok\fi{}{%else 610 | \bxgw@exec@get 611 | \bxgw@cond\ifx\bxgw@resp\@empty\fi{\bxgw@abort}{}% 612 | \bxgw@save@bb 613 | \ifbxgw@use@bb \let\bxgw@bb\bxgw@resp \fi 614 | }% 615 | \IfFileExists{\bxgw@glyph@file}{% 616 | \bxgw@use@img{\bxgw@glyph@file}% 617 | }{%else 618 | \PackageError\bxgw@pkgname 619 | {Glyph image of '\bxgw@glyph'(\bxgw@rev) is not found}\@ehd 620 | \bxgw@abort 621 | }% 622 | } 623 | 624 | %% \bxgw@save@bb 625 | \def\bxgw@save@bb{% 626 | \def\bxgw@tmpb{ok}\ifx\bxgw@tmpb\bxgw@resp\else 627 | \immediate\openout\bxgw@out=\bxgw@bbox@file\relax 628 | \immediate\write\bxgw@out{\noexpand\do{\bxgw@resp}\relax}% 629 | \immediate\closeout\bxgw@out 630 | \fi 631 | } 632 | 633 | %--------------------------------------- string validation 634 | 635 | %% \bxgw@validate{} 636 | \def\bxgw@validate#1{% 637 | \bxgw@oktrue 638 | \ifx\bxgw@end#1\bxgw@end \bxgw@okfalse 639 | \else 640 | \@firstofone{\bxgw@validate@b#1} \bxgw@end 641 | \ifbxgw@ok \bxgw@validate@c{#1}\fi 642 | \fi 643 | } 644 | \def\bxgw@validate@a#1{\lccode`#1`*\relax} 645 | \def\bxgw@validate@b#1 #2\bxgw@end{% 646 | \ifx\bxgw@end#2\bxgw@end\else \bxgw@okfalse \fi 647 | } 648 | \def\bxgw@validate@c#1{% 649 | \begingroup 650 | \escapechar`\\\def\bxgw@tmpa{#1}% 651 | \expandafter\bxgw@validate@d\meaning\bxgw@tmpa\bxgw@end 652 | \endgroup 653 | } 654 | \def\bxgw@validate@d#1>#2\bxgw@end{% 655 | \let\do\bxgw@validate@a \bxgw@ban@list 656 | \lowercase{\bxgw@validate@e#2}*\bxgw@end 657 | } 658 | \def\bxgw@validate@e#1*#2\bxgw@end{% 659 | \ifx\bxgw@end#2\bxgw@end\else \aftergroup\bxgw@okfalse \fi 660 | } 661 | \def\bxgw@ban@list{% 662 | \do\"\do\$\do\@\do\&\do\'\do\(\do\)\do\^\do\|\do\[% 663 | \do\]\do\{\do\}\do\;\do\*\do\?\do\<\do\>\do\\% 664 | } 665 | 666 | %% \bxgw@validate@var\CS 667 | \def\bxgw@validate@var#1{% 668 | \expandafter\bxgw@validate\expandafter{#1}% 669 | } 670 | 671 | %--------------------------------------- external 672 | 673 | %% \bxgw@spawn{}{...} 674 | \def\bxgw@spawn#1#2{% 675 | \pdf@system{\bxgw@command\space +#1\space 676 | \bxgw@wdir\space \bxgw@img@fmt\space 677 | \bxgw@drv\space #2}% 678 | \bxgw@read@response 679 | \ifx\bxgw@resp\@empty 680 | \PackageError\bxgw@pkgname 681 | {Helper command failed for some reason}\@ehd 682 | \fi 683 | } 684 | 685 | %% \bxgw@read@response 686 | \def\bxgw@read@response{% 687 | \let\bxgw@resp\@empty 688 | \let\do\bxgw@read@response@a 689 | \InputIfFileExists{\bxgw@resp@file}{}{}% 690 | } 691 | \def\bxgw@read@response@a#1{\def\bxgw@resp{#1}} 692 | 693 | %% \bxgw@exec@ping 694 | \def\bxgw@exec@ping{% 695 | \ifnum\ifx\pdf@shellescape\@undefined \tw@ 696 | \else \pdf@shellescape \fi >\z@ 697 | \def\bxgw@token{ping}% 698 | \bxgw@read@response 699 | \ifx\bxgw@token\bxgw@resp \def\bxgw@token{ping+}\fi 700 | \bxgw@spawn{ping}{\bxgw@token}% 701 | \bxgw@read@response 702 | \ifx\bxgw@token\bxgw@resp 703 | \PackageInfo\bxgw@pkgname 704 | {PING success\@gobble}% 705 | \else 706 | \PackageError\bxgw@pkgname 707 | {PING failure, download aborted}% 708 | {Run your LaTeX command with -shell-escape option to download 709 | images.\MessageBreak\@ehc}% 710 | \chardef\bxgw@download\z@ 711 | \fi 712 | \else 713 | \PackageError\bxgw@pkgname 714 | {Shell is disabled, download aborted}% 715 | {Run your LaTeX command with -shell-escape option to download 716 | images.\MessageBreak\@ehc}% 717 | \chardef\bxgw@download\z@ 718 | \fi 719 | } 720 | 721 | %% \bxgw@exec@info 722 | \def\bxgw@exec@info{% 723 | \bxgw@spawn{info}{\bxgw@glyph}% 724 | } 725 | 726 | %% \bxgw@exec@get 727 | \def\bxgw@exec@get{% 728 | \bxgw@spawn{get}{\bxgw@glyph\space\bxgw@rev}% 729 | } 730 | 731 | %--------------------------------------- directlua mode 732 | \ifbxgw@internallua 733 | 734 | %% \bxgw@spawn{}{...} 735 | \def\bxgw@spawn#1#2{% 736 | \edef\bxgw@resp{\unexpanded\expandafter{\directlua{ 737 | tex.sprint(bxglyphwiki.spawn("#1\space 738 | \bxgw@wdir\space \bxgw@img@fmt\space 739 | \bxgw@drv\space #2")) 740 | }}}% 741 | \ifx\bxgw@resp\@empty 742 | \PackageError\bxgw@pkgname 743 | {Helper command failed for some reason}\@ehd 744 | \fi 745 | } 746 | 747 | %% \bxgw@exec@ping 748 | \def\bxgw@exec@ping{% 749 | \bxgw@spawn{ping}{int}% 750 | \unless\ifx\bxgw@resp\@empty 751 | \PackageInfo\bxgw@pkgname 752 | {PING success\@gobble}% 753 | \else 754 | \PackageError\bxgw@pkgname 755 | {PING failure, download aborted}% 756 | {Run your LaTeX command with -shell-restricted (or -shell-escape) 757 | option\MessageBreak to download images.\MessageBreak\@ehc}% 758 | \chardef\bxgw@download\z@ 759 | \fi 760 | } 761 | 762 | %% \bxgw@read@response 763 | \let\bxgw@read@response\relax 764 | \let\bxgw@read@response@a\relax 765 | 766 | \fi 767 | %--------------------------------------- start up 768 | 769 | %% validate static parameters 770 | \bxgw@oktrue 771 | \def\bxgw@next#1#2{% 772 | \ifbxgw@ok 773 | \bxgw@validate@var#1\ifbxgw@ok\else 774 | \PackageError\bxgw@pkgname 775 | {#2 '#1' is illegal}% 776 | {Some of the setting has an illegal value.\MessageBreak 777 | The package will not work at all.\MessageBreak\@ehc}% 778 | \bxgw@annihilate 779 | \fi 780 | \fi 781 | } 782 | \bxgw@next\bxgw@wdir{Directory name} 783 | \bxgw@next\bxgw@img@fmt{Format name} 784 | \bxgw@next\bxgw@drv{Driver name} 785 | 786 | %% invoke ping 787 | \ifnum\bxgw@download>\z@ 788 | \bxgw@exec@ping 789 | \fi 790 | 791 | %% read saved bbox 792 | \ifx t\bxgw@annihilated\else 793 | \ifbxgw@use@bb 794 | \def\do{\def\bxgw@bb}% 795 | \InputIfFileExists{\bxgw@bbox@file}{}{}% 796 | \fi 797 | \fi 798 | 799 | %--------------------------------------- all done 800 | \endinput 801 | %% EOF 802 | -------------------------------------------------------------------------------- /articles/sty/jumoline.sty: -------------------------------------------------------------------------------- 1 | %% 2 | %% This is file `jumoline.sty', 3 | %% generated with the docstrip utility. 4 | %% 5 | %% The original source files were: 6 | %% 7 | %% jumoline.dtx (with options: `package') 8 | %% 9 | %% IMPORTANT NOTICE: 10 | %% 11 | %% For the copyright see the source file. 12 | %% 13 | %% You are *not* allowed to modify this file. 14 | %% 15 | %% You are *not* allowed to distribute this file. 16 | %% For distribution of the original source see the terms 17 | %% for copying and modification in the file jumoline.dtx. 18 | %% 19 | %% Style file `jumoline'. 20 | %% Copyright (C) 1999-2001 Hiroshi Nakashima 21 | %% (Toyohashi Univ. of Tech.) 22 | %% 23 | %% This program can be redistributed and/or modified under the terms 24 | %% of the LaTeX Project Public License distributed from CTAN 25 | %% archives in directory macros/latex/base/lppl.txt; either 26 | %% version 1 of the License, or any later version. 27 | %% 28 | %% \CharacterTable 29 | %% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z 30 | %% Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z 31 | %% Digits \0\1\2\3\4\5\6\7\8\9 32 | %% Exclamation \! Double quote \" Hash (number) \# 33 | %% Dollar \$ Percent \% Ampersand \& 34 | %% Acute accent \' Left paren \( Right paren \) 35 | %% Asterisk \* Plus \+ Comma \, 36 | %% Minus \- Point \. Solidus \/ 37 | %% Colon \: Semicolon \; Less than \< 38 | %% Equals \= Greater than \> Question mark \? 39 | %% Commercial at \@ Left bracket \[ Backslash \\ 40 | %% Right bracket \] Circumflex \^ Underscore \_ 41 | %% Grave accent \` Left brace \{ Vertical bar \| 42 | %% Right brace \} Tilde \~} 43 | %% 44 | %% 45 | \def\next{LaTeX2e} 46 | \ifx\fmtname\next 47 | \def\next{ 48 | \NeedsTeXFormat{LaTeX2e}[1994/12/01] 49 | \ProvidesPackage{jumoline}} 50 | \else\def\next[#1]{}\fi 51 | \next 52 | [2001/05/31 v1.2 ] 53 | 54 | \ifx\PackageError\undefined 55 | \def\PackageError#1#2#3{\@latexerr{#1:#2}{#3^^J\@ehc}} 56 | \fi 57 | 58 | %%^L 59 | 60 | %% Register Declaration 61 | 62 | \newdimen\UnderlineDepth \UnderlineDepth-\maxdimen 63 | \newdimen\MidlineHeight \MidlineHeight-\maxdimen 64 | \newdimen\OverlineHeight \OverlineHeight-\maxdimen 65 | \newdimen\UMOlineThickness \UMOlineThickness.4pt 66 | 67 | \newdimen\UMO@height \newdimen\UMO@depth 68 | \newdimen\UMO@dqspace \newdimen\UMO@tempdim 69 | 70 | \newskip\UMO@prejfmglue \newskip\UMO@postjfmglue 71 | 72 | \newcount\UMO@mode 73 | \let\UMO@afterblock\z@ 74 | \let\UMO@afterword\@ne 75 | \let\UMO@afterchar\tw@ 76 | 77 | \newcount\UMO@spacefactor 78 | \newcount\UMO@firstxspcode 79 | \newcount\UMO@lastxspcode 80 | \newcount\UMO@inhibitxspcode 81 | \newcount\UMO@prebreakpenalty 82 | \newcount\UMO@postbreakpenalty 83 | \newcount\UMO@kpostbreakpenalty 84 | 85 | \newif\ifUMO@nospace \newif\ifUMO@firstelem 86 | 87 | %%^L 88 | 89 | %% User Interface and Initialization 90 | 91 | \def\Underline{\UMO@line\UnderlineDepth{-\UnderlineDepth}{-\dp\strutbox}} 92 | \def\Midline{\setbox\@tempboxa\hbox{$B$"(B}% 93 | \UMO@line\MidlineHeight\MidlineHeight{.5\ht\@tempboxa}} 94 | \def\Overline{\UMO@line\OverlineHeight\OverlineHeight{\ht\strutbox}} 95 | \def\UMOline{\UMO@line{-\maxdimen}\z@} 96 | 97 | \def\UMO@line#1#2#3#4{\begingroup \let\\\UMOnewline 98 | \relax\ifdim#1<\z@ \UMO@height#3\relax 99 | \else \UMO@height#2\relax \fi 100 | \UMO@depth-\UMO@height 101 | \ifdim\UMO@height<\z@ \advance\UMO@depth\UMOlineThickness 102 | \else \advance\UMO@height\UMOlineThickness \fi 103 | \settowidth\UMO@dqspace{$B!H(B}\advance\UMO@dqspace-1zw 104 | \UMO@dqspace-\UMO@dqspace \divide\UMO@dqspace\tw@ 105 | \UMO@nospacetrue \UMO@firstelemtrue 106 | \UMO@mode\UMO@afterblock 107 | \ifvmode\leavevmode\fi 108 | \def\@tempa{#4 }\edef\@tempb{\noexpand\@nil\space}% 109 | \expandafter\expandafter\expandafter\UMO@wordloop 110 | \expandafter\@tempa\@tempb 111 | \endgroup \UMO@aftergroup} 112 | 113 | %%^L 114 | 115 | %% Processing Word Elements 116 | 117 | \def\UMO@wordloop{\UMO@ifbgroup\UMO@wordblock\UMO@iwordloop} 118 | \def\UMO@ifbgroup#1#2{\let\@tempa#1\let\@tempb#2\futurelet\@tempc\UMO@ifnc} 119 | \def\UMO@ifnc{\ifx\@tempc\bgroup \let\next\@tempa \else\let\next\@tempb \fi 120 | \next} 121 | \def\UMO@wordblock#1{\UMO@spaceskip 122 | \UMO@putbox\relax{#1}\UMO@nospacetrue \UMO@mode\UMO@afterblock 123 | \UMO@spacefactor\@m \UMO@wordloop} 124 | \def\UMO@iwordloop#1 {\def\@tempa{#1}\ifx\@tempa\@nnil \let\next\UMO@end 125 | \else 126 | \ifx\@tempa\empty \UMO@nospacefalse 127 | \else 128 | \UMO@spaceskip \UMO@mode\UMO@afterblock 129 | \def\UMO@theword{}\UMO@firstxspcode\m@ne 130 | \UMO@charloop#1\@nil \fi 131 | \let\next\UMO@wordloop \fi 132 | \next} 133 | 134 | %%^L 135 | 136 | %% Interword Spacing 137 | 138 | \def\UMO@spaceskip{\ifUMO@nospace \UMO@nospacefalse \else 139 | \ifdim\spaceskip=\z@ 140 | \@tempdima\fontdimen3\font\relax 141 | \multiply\@tempdima\UMO@spacefactor \divide\@tempdima\@m 142 | \@tempdimb\fontdimen4\font\relax \multiply\@tempdimb\@m 143 | \divide\@tempdimb\UMO@spacefactor 144 | \@tempskipa\fontdimen2\font plus\@tempdima minus\@tempdimb\relax 145 | \else 146 | \edef\@tempa{\the\spaceskip\space @ @ @ @ }% 147 | \expandafter\UMO@setspaceskip\@tempa\@nil 148 | \fi 149 | \ifnum\UMO@spacefactor<2000\else 150 | \ifdim\xspaceskip=\z@ \advance\@tempskipa\fontdimen7\font 151 | \else \@tempskipa\xspaceskip 152 | \fi\fi 153 | \UMO@skip\@tempskipa \fi} 154 | \def\UMO@setspaceskip#1 #2 #3 #4 #5 #6\@nil{\@tempdima\z@ \@tempdimb\z@ 155 | \def\@tempa{#2}\def\@tempb{#3}% 156 | \ifx\@tempa\UMO@plus \@tempdima#3\def\@tempa{#4}\def\@tempb{#5}\fi 157 | \ifx\@tempa\UMO@minus \@tempdimb\@tempb\relax\fi 158 | \multiply\@tempdima\UMO@specefactor \divide\@tempdima\@m 159 | \multiply\@tempdimb\@m \divide\UMO@spacefactor 160 | \@tempskipa#1 plus\@tempdima minus\@tempdimb\relax} 161 | \def\@tempa#1 #2 #3 #4 #5\@nil{\def\UMO@plus{#2}\def\UMO@minus{#4}} 162 | \@tempskipa1pt plus 2pt minus 3pt 163 | \expandafter\@tempa\the\@tempskipa\@nil 164 | 165 | %%^L 166 | 167 | %% Processing Characters 168 | 169 | \def\UMO@charloop{\UMO@ifbgroup\UMO@charblock\UMO@icharloop} 170 | \def\UMO@charblock#1{\UMO@putword 171 | \UMO@putbox\relax{#1}\UMO@mode\UMO@afterblock \UMO@spacefactor\@m 172 | \UMO@charloop} 173 | \def\UMO@icharloop#1{\def\@tempa{#1}% 174 | \ifx\@tempa\@nnil \UMO@putword \let\next\relax 175 | \else\ifx\UMOspace#1\relax \UMO@putword \let\next\UMO@space 176 | \else\ifx\UMOnewline#1\relax \UMO@putword \let\next\UMO@newline 177 | \else 178 | \ifnum`#1<256\relax \edef\UMO@theword{\UMO@theword#1}% 179 | \ifnum\UMO@firstxspcode<\z@ 180 | \UMO@firstxspcode\xspcode`#1\relax 181 | \UMO@prebreakpenalty\prebreakpenalty`#1\relax 182 | \fi 183 | \UMO@lastxspcode\xspcode`#1\relax 184 | \UMO@postbreakpenalty\postbreakpenalty`#1\relax 185 | \else \UMO@putword \UMO@putchar{#1}\UMO@spacefactor\@m\fi 186 | \let\next\UMO@charloop \fi\fi\fi \next} 187 | \def\UMOspace{\PackageError{jumoline}% 188 | {\string\UMOspace\space cannot be used here.}% 189 | {\string\UMOspace\space can be used only in the argument of 190 | \string\Underline\space and its relatives.}} 191 | \def\UMOnewline{\PackageError{jumoline}% 192 | {\string\UMOnewline\space cannot be used here.}% 193 | {\string\UMOnewline\space can be used only in the argument of 194 | \string\Underline\space and its relatives.}} 195 | 196 | %%^L 197 | 198 | %% Put ASCII String 199 | 200 | \def\UMO@putword{\ifx\UMO@theword\empty\else 201 | \ifnum\UMO@mode=\UMO@afterchar 202 | \advance\UMO@kpostbreakpenalty\UMO@prebreakpenalty 203 | \penalty\UMO@kpostbreakpenalty 204 | \ifdim\UMO@postjfmglue>\z@ \UMO@skip\UMO@postjfmglue 205 | \else\ifodd\UMO@inhibitxspcode \ifodd\UMO@firstxspcode 206 | \UMO@skip\xkanjiskip \fi\fi\fi\fi 207 | \setbox\@tempboxa\hbox{% 208 | \UMO@theword\global\UMO@spacefactor\spacefactor}% 209 | \UMO@putbox\relax\UMO@theword \UMO@mode\UMO@afterword 210 | \def\UMO@theword{}\fi \UMO@firstxspcode\m@ne} 211 | 212 | %%^L 213 | 214 | %% Put Kanji Letter 215 | 216 | \def\UMO@putchar#1{% 217 | \ifnum\UMO@mode=\UMO@afterchar \UMO@prejfmglue\UMO@postjfmglue 218 | \else \UMO@prejfmglue\z@ \fi 219 | \UMO@postjfmglue\z@ 220 | \ifnum`#1<\kuten"1001\relax\UMO@setjfmglue{#1}\fi 221 | \@tempskipa\UMO@prejfmglue 222 | \UMO@inhibitxspcode\inhibitxspcode`#1\relax 223 | \@tempcnta\prebreakpenalty`#1\relax 224 | \ifnum\UMO@mode=\UMO@afterchar 225 | \advance\@tempcnta\UMO@kpostbreakpenalty 226 | \ifdim\UMO@prejfmglue=\z@ \@tempskipa\kanjiskip \fi 227 | \else\ifnum\UMO@mode=\UMO@afterword 228 | \advance\@tempcnta\UMO@postbreakpenalty 229 | \ifdim\UMO@prejfmglue=\z@ 230 | \ifnum\UMO@lastxspcode>\@ne \ifnum\UMO@inhibitxspcode>\@ne 231 | \@tempskipa\xkanjiskip \fi\fi\fi\fi\fi 232 | \penalty\@tempcnta 233 | \edef\@tempa{\the\@tempskipa}\ifx\@tempa\UMO@zskip\else 234 | \UMO@skip\@tempskipa \fi 235 | \UMO@putbox\inhibitglue{#1}% 236 | \UMO@kpostbreakpenalty\postbreakpenalty`#1\relax 237 | \UMO@mode\UMO@afterchar} 238 | \@tempskipa\z@ 239 | \edef\UMO@zskip{\the\@tempskipa} 240 | \def\UMO@setjfmglue#1{% 241 | \settowidth\@tempdima{$B$"(B#1}\settowidth\@tempdimb{$B$"(B\inhibitglue#1}% 242 | \advance\@tempdima-\@tempdimb 243 | \settowidth\UMO@tempdim{#1$B$"(B}\settowidth\@tempdimb{#1\inhibitglue $B$"(B}% 244 | \advance\UMO@tempdim-\@tempdimb 245 | \ifdim\@tempdima>\z@ 246 | \ifdim\UMO@tempdim>\z@ 247 | \@tempskipa\@tempdima minus\@tempdima\relax 248 | \UMO@postjfmglue\UMO@tempdim minus\UMO@tempdim\relax 249 | \else \@tempskipa\@tempdima minus\UMO@dqspace\relax \fi 250 | \advance\UMO@prejfmglue\@tempskipa 251 | \else \UMO@postjfmglue\UMO@tempdim minus\UMO@dqspace \fi} 252 | 253 | %%^L 254 | 255 | %% Draw Under/Mid/Overline 256 | 257 | \def\UMO@putbox#1#2{\setbox\@tempboxa\hbox{#1#2#1}\@tempdima\wd\@tempboxa 258 | \ifUMO@firstelem\else 259 | \rlap{\vrule\@height\UMO@height\@depth\UMO@depth\@width\@tempdima}\fi 260 | \box\@tempboxa 261 | \ifUMO@firstelem \UMO@firstelemfalse 262 | \llap{\vrule\@height\UMO@height\@depth\UMO@depth\@width\@tempdima}\fi} 263 | \def\UMO@skip#1{% 264 | \leaders\hrule\@height\UMO@height\@depth\UMO@depth\hskip#1\relax} 265 | 266 | %%^L 267 | 268 | %% Explicit Spacing and Line Breaking 269 | 270 | \def\UMO@space{\UMO@mode\UMO@afterblock 271 | \@ifstar\UMO@sspace\UMO@ispace} 272 | \def\UMO@sspace#1{\vrule width\z@\nobreak\UMO@skip{#1}\UMO@charloop} 273 | \def\UMO@ispace#1{\@tempskipa#1\relax 274 | \@ifstar{\@tempswafalse\UMO@iispace}{\@tempswatrue\UMO@iispace}} 275 | \def\UMO@iispace{\@ifnextchar[%] 276 | {\UMO@penalty}% 277 | {\UMO@skip\@tempskipa \UMO@charloop}} 278 | \def\UMO@penalty[#1]{\@tempcnta#1\relax 279 | \if@tempswa 280 | \ifnum\@tempcnta<\z@ \@tempcnta-\@tempcnta \fi 281 | \ifcase\@tempcnta \or 282 | \@tempcnta\@lowpenalty \or 283 | \@tempcnta\@medpenalty \or 284 | \@tempcnta\@highpenalty \else 285 | \@tempcnta\@M \fi 286 | \ifnum#1<\z@ \@tempcnta-\@tempcnta \fi \fi 287 | \penalty\@tempcnta \UMO@skip\@tempskipa \UMO@charloop} 288 | 289 | \def\UMO@newline{\UMO@mode\UMO@afterblock 290 | \@ifstar{\UMO@skip{0pt plus1fil}\break \UMO@charloop}% 291 | {\hfil \break \UMO@charloop}} 292 | 293 | %%^L 294 | 295 | %% Finalization 296 | 297 | \def\UMO@end{\ifnum\UMO@mode=\UMO@afterchar 298 | \ifnum\UMO@kpostbreakpenalty>\z@ 299 | \penalty\UMO@kpostbreakpenalty \fi 300 | \ifdim\UMO@postjfmglue>\z@ 301 | \UMO@skip\UMO@postjfmglue\fi \fi 302 | \xdef\UMO@aftergroup{\ifnum\UMO@mode=\UMO@afterword 303 | \spacefactor\number\UMO@spacefactor\fi}} 304 | \endinput 305 | %% 306 | %% End of file `jumoline.sty'. 307 | -------------------------------------------------------------------------------- /articles/sty/pdftexcmds.sty: -------------------------------------------------------------------------------- 1 | %% 2 | %% This is file `pdftexcmds.sty', 3 | %% generated with the docstrip utility. 4 | %% 5 | %% The original source files were: 6 | %% 7 | %% pdftexcmds.dtx (with options: `package') 8 | %% 9 | %% This is a generated file. 10 | %% 11 | %% Copyright (C) 2007, 2009, 2010 by 12 | %% Heiko Oberdiek 13 | %% 14 | %% This work may be distributed and/or modified under the 15 | %% conditions of the LaTeX Project Public License, either 16 | %% version 1.3c of this license or (at your option) any later 17 | %% version. This version of this license is in 18 | %% http://www.latex-project.org/lppl/lppl-1-3c.txt 19 | %% and the latest version of this license is in 20 | %% http://www.latex-project.org/lppl.txt 21 | %% and version 1.3 or later is part of all distributions of 22 | %% LaTeX version 2005/12/01 or later. 23 | %% 24 | %% This work has the LPPL maintenance status "maintained". 25 | %% 26 | %% This Current Maintainer of this work is Heiko Oberdiek. 27 | %% 28 | %% The Base Interpreter refers to any `TeX-Format', 29 | %% because some files are installed in TDS:tex/generic//. 30 | %% 31 | %% This work consists of the main source file pdftexcmds.dtx 32 | %% and the derived files 33 | %% pdftexcmds.sty, pdftexcmds.pdf, pdftexcmds.ins, pdftexcmds.drv, 34 | %% pdftexcmds-test1.tex, pdftexcmds-test2.tex, 35 | %% oberdiek.pdftexcmds.lua, pdftexcmds.lua. 36 | %% 37 | \begingroup 38 | \catcode44 12 % , 39 | \catcode45 12 % - 40 | \catcode46 12 % . 41 | \catcode58 12 % : 42 | \catcode64 11 % @ 43 | \catcode123 1 % { 44 | \catcode125 2 % } 45 | \expandafter\let\expandafter\x\csname ver@pdftexcmds.sty\endcsname 46 | \ifx\x\relax % plain-TeX, first loading 47 | \else 48 | \def\empty{}% 49 | \ifx\x\empty % LaTeX, first loading, 50 | % variable is initialized, but \ProvidesPackage not yet seen 51 | \else 52 | \catcode35 6 % # 53 | \expandafter\ifx\csname PackageInfo\endcsname\relax 54 | \def\x#1#2{% 55 | \immediate\write-1{Package #1 Info: #2.}% 56 | }% 57 | \else 58 | \def\x#1#2{\PackageInfo{#1}{#2, stopped}}% 59 | \fi 60 | \x{pdftexcmds}{The package is already loaded}% 61 | \aftergroup\endinput 62 | \fi 63 | \fi 64 | \endgroup 65 | \begingroup 66 | \catcode35 6 % # 67 | \catcode40 12 % ( 68 | \catcode41 12 % ) 69 | \catcode44 12 % , 70 | \catcode45 12 % - 71 | \catcode46 12 % . 72 | \catcode47 12 % / 73 | \catcode58 12 % : 74 | \catcode64 11 % @ 75 | \catcode91 12 % [ 76 | \catcode93 12 % ] 77 | \catcode123 1 % { 78 | \catcode125 2 % } 79 | \expandafter\ifx\csname ProvidesPackage\endcsname\relax 80 | \def\x#1#2#3[#4]{\endgroup 81 | \immediate\write-1{Package: #3 #4}% 82 | \xdef#1{#4}% 83 | }% 84 | \else 85 | \def\x#1#2[#3]{\endgroup 86 | #2[{#3}]% 87 | \ifx#1\@undefined 88 | \xdef#1{#3}% 89 | \fi 90 | \ifx#1\relax 91 | \xdef#1{#3}% 92 | \fi 93 | }% 94 | \fi 95 | \expandafter\x\csname ver@pdftexcmds.sty\endcsname 96 | \ProvidesPackage{pdftexcmds}% 97 | [2010/03/01 v0.8 Utility functions of pdfTeX for LuaTeX (HO)] 98 | \begingroup 99 | \catcode123 1 % { 100 | \catcode125 2 % } 101 | \def\x{\endgroup 102 | \expandafter\edef\csname pdftexcmds@AtEnd\endcsname{% 103 | \catcode35 \the\catcode35\relax 104 | \catcode64 \the\catcode64\relax 105 | \catcode123 \the\catcode123\relax 106 | \catcode125 \the\catcode125\relax 107 | }% 108 | }% 109 | \x 110 | \catcode35 6 % # 111 | \catcode64 11 % @ 112 | \catcode123 1 % { 113 | \catcode125 2 % } 114 | \def\TMP@EnsureCode#1#2{% 115 | \edef\pdftexcmds@AtEnd{% 116 | \pdftexcmds@AtEnd 117 | \catcode#1 \the\catcode#1\relax 118 | }% 119 | \catcode#1 #2\relax 120 | } 121 | \TMP@EnsureCode{10}{12}% ^^J 122 | \TMP@EnsureCode{33}{12}% ! 123 | \TMP@EnsureCode{34}{12}% " 124 | \TMP@EnsureCode{39}{12}% ' 125 | \TMP@EnsureCode{40}{12}% ( 126 | \TMP@EnsureCode{41}{12}% ) 127 | \TMP@EnsureCode{42}{12}% * 128 | \TMP@EnsureCode{43}{12}% + 129 | \TMP@EnsureCode{44}{12}% , 130 | \TMP@EnsureCode{45}{12}% - 131 | \TMP@EnsureCode{46}{12}% . 132 | \TMP@EnsureCode{47}{12}% / 133 | \TMP@EnsureCode{58}{12}% : 134 | \TMP@EnsureCode{60}{12}% < 135 | \TMP@EnsureCode{61}{12}% = 136 | \TMP@EnsureCode{62}{12}% > 137 | \TMP@EnsureCode{94}{7}% ^ (superscript) 138 | \TMP@EnsureCode{95}{12}% _ (other) 139 | \TMP@EnsureCode{96}{12}% ` 140 | \TMP@EnsureCode{126}{12}% ~ (other) 141 | \edef\pdftexcmds@AtEnd{% 142 | \pdftexcmds@AtEnd 143 | \escapechar=\number\escapechar\relax 144 | } 145 | \escapechar=92 % 146 | \begingroup\expandafter\expandafter\expandafter\endgroup 147 | \expandafter\ifx\csname RequirePackage\endcsname\relax 148 | \input infwarerr.sty\relax 149 | \input ifluatex.sty\relax 150 | \input ltxcmds.sty\relax 151 | \else 152 | \RequirePackage{infwarerr}[2007/09/09]% 153 | \RequirePackage{ifluatex}[2010/03/01]% 154 | \RequirePackage{ltxcmds}% 155 | \fi 156 | \ifluatex 157 | \else 158 | \@PackageInfoNoLine{pdftexcmds}{LuaTeX not detected}% 159 | \def\pdftexcmds@nopdftex{% 160 | \@PackageInfoNoLine{pdftexcmds}{pdfTeX >= 1.30 not detected}% 161 | \let\pdftexcmds@nopdftex\relax 162 | }% 163 | \def\pdftexcmds@temp#1{% 164 | \begingroup\expandafter\expandafter\expandafter\endgroup 165 | \expandafter\ifx\csname pdf#1\endcsname\relax 166 | \pdftexcmds@nopdftex 167 | \else 168 | \expandafter\def\csname pdf@#1\expandafter\endcsname 169 | \expandafter##\expandafter{% 170 | \csname pdf#1\endcsname 171 | }% 172 | \fi 173 | }% 174 | \pdftexcmds@temp{strcmp}% 175 | \pdftexcmds@temp{escapehex}% 176 | \let\pdf@escapehexnative\pdf@escapehex 177 | \pdftexcmds@temp{unescapehex}% 178 | \let\pdf@unescapehexnative\pdf@unescapehex 179 | \pdftexcmds@temp{escapestring}% 180 | \pdftexcmds@temp{escapename}% 181 | \pdftexcmds@temp{filesize}% 182 | \pdftexcmds@temp{filemoddate}% 183 | \begingroup\expandafter\expandafter\expandafter\endgroup 184 | \expandafter\ifx\csname pdfshellescape\endcsname\relax 185 | \pdftexcmds@nopdftex 186 | \else 187 | \def\pdf@shellescape{% 188 | \pdfshellescape 189 | }% 190 | \fi 191 | \begingroup\expandafter\expandafter\expandafter\endgroup 192 | \expandafter\ifx\csname pdffiledump\endcsname\relax 193 | \pdftexcmds@nopdftex 194 | \else 195 | \def\pdf@filedump#1#2#3{% 196 | \pdffiledump offset#1 length#2{#3}% 197 | }% 198 | \fi 199 | \begingroup\expandafter\expandafter\expandafter\endgroup 200 | \expandafter\ifx\csname pdfmdfivesum\endcsname\relax 201 | \pdftexcmds@nopdftex 202 | \else 203 | \def\pdf@mdfivesum#{\pdfmdfivesum}% 204 | \let\pdf@mdfivesumnative\pdf@mdfivesum 205 | \def\pdf@filemdfivesum#{\pdfmdfivesum file}% 206 | \fi 207 | \def\pdf@system#{% 208 | \immediate\write18% 209 | }% 210 | \fi 211 | \ifluatex 212 | \ifnum\luatexversion<36 % 213 | \def\pdftexcmds@directlua{\directlua0 }% 214 | \else 215 | \let\pdftexcmds@directlua\directlua 216 | \fi 217 | \begingroup 218 | \newlinechar=10 % 219 | \endlinechar=\newlinechar 220 | \pdftexcmds@directlua{% 221 | if tex.enableprimitives then 222 | tex.enableprimitives('pdf@', {'primitive', 'ifprimitive'}) 223 | tex.enableprimitives('', {'luaescapestring'}) 224 | end 225 | }% 226 | \endgroup % 227 | \fi 228 | \def\pdftexcmds@strip@prefix#1>{} 229 | \def\pdftexcmds@temp#1#2#3{% 230 | \begingroup\expandafter\expandafter\expandafter\endgroup 231 | \expandafter\ifx\csname pdf@#1\endcsname\relax 232 | \begingroup 233 | \def\x{#3}% 234 | \edef\x{\expandafter\pdftexcmds@strip@prefix\meaning\x}% 235 | \escapechar=-1 % 236 | \edef\y{\expandafter\meaning\csname#2\endcsname}% 237 | \expandafter\endgroup 238 | \ifx\x\y 239 | \expandafter\let\csname pdf@#1\expandafter\endcsname 240 | \csname #2\endcsname 241 | \fi 242 | \fi 243 | } 244 | \pdftexcmds@temp{primitive}{pdfprimitive}{pdfprimitive}% pdfTeX, LuaTeX 245 | \pdftexcmds@temp{primitive}{primitive}{primitive}% XeTeX 246 | \pdftexcmds@temp{primitive}{luatexprimitive}{pdfprimitive}% LuaTeX 247 | \pdftexcmds@temp{primitive}{luatexpdfprimitive}{pdfprimitive}% LuaTeX 248 | \pdftexcmds@temp{ifprimitive}{ifpdfprimitive}{ifpdfprimitive}% pdfTeX, LuaTeX 249 | \pdftexcmds@temp{ifprimitive}{ifprimitive}{ifprimitive}% XeTeX 250 | \pdftexcmds@temp{ifprimitive}{luatexifprimitive}{ifpdfprimitive}% LuaTeX 251 | \pdftexcmds@temp{ifprimitive}{luatexifpdfprimitive}{ifpdfprimitive}% LuaTeX 252 | \begingroup 253 | \expandafter\ifx\csname pdf@primitive\endcsname\relax 254 | \else 255 | \expandafter\ifx\csname pdftexversion\endcsname\relax 256 | \else 257 | \ifnum\pdftexversion=140 % 258 | \expandafter\ifx\csname pdftexrevision\endcsname\relax 259 | \else 260 | \ifnum\pdftexrevision<4 % 261 | \endgroup 262 | \let\pdf@primitive\@undefined 263 | \@PackageInfoNoLine{pdftexcmds}{% 264 | \string\pdf@primitive disabled, because\MessageBreak 265 | \string\pdfprimitive\space is broken until pdfTeX 1.40.4% 266 | }% 267 | \begingroup 268 | \fi 269 | \fi 270 | \fi 271 | \fi 272 | \fi 273 | \endgroup 274 | \begingroup 275 | \@PackageInfoNoLine{pdftexcmds}{% 276 | \string\pdf@primitive\space is % 277 | \expandafter\ifx\csname pdf@primitive\endcsname\relax not \fi 278 | available% 279 | }% 280 | \@PackageInfoNoLine{pdftexcmds}{% 281 | \string\pdf@ifprimitive\space is % 282 | \expandafter\ifx\csname pdf@ifprimitive\endcsname\relax not \fi 283 | available% 284 | }% 285 | \endgroup 286 | \def\pdftexcmds@temp#1{% 287 | \begingroup\expandafter\expandafter\expandafter\endgroup 288 | \expandafter\ifx\csname pdf@#1\endcsname\relax 289 | \begingroup 290 | \escapechar=-1 % 291 | \edef\x{\expandafter\meaning\csname#1\endcsname}% 292 | \def\y{#1}% 293 | \def\z##1->{}% 294 | \edef\y{\expandafter\z\meaning\y}% 295 | \expandafter\endgroup 296 | \ifx\x\y 297 | \expandafter\def\csname pdf@#1\expandafter\endcsname 298 | \expandafter{% 299 | \csname#1\endcsname 300 | }% 301 | \fi 302 | \fi 303 | }% 304 | \pdftexcmds@temp{shellescape}% 305 | \pdftexcmds@temp{strcmp}% 306 | \def\pdf@isprimitive{% 307 | \begingroup\expandafter\expandafter\expandafter\endgroup 308 | \expandafter\ifx\csname pdf@strcmp\endcsname\relax 309 | \long\def\pdf@isprimitive##1{% 310 | \expandafter\pdftexcmds@isprimitive\expandafter{\meaning##1}% 311 | }% 312 | \long\def\pdftexcmds@isprimitive##1##2{% 313 | \expandafter\pdftexcmds@@isprimitive\expandafter{\string##2}{##1}% 314 | }% 315 | \def\pdftexcmds@@isprimitive##1##2{% 316 | \ifnum0\pdftexcmds@equal##1\delimiter##2\delimiter=1 % 317 | \expandafter\ltx@firstoftwo 318 | \else 319 | \expandafter\ltx@secondoftwo 320 | \fi 321 | }% 322 | \def\pdftexcmds@equal##1##2\delimiter##3##4\delimiter{% 323 | \ifx##1##3% 324 | \ifx\relax##2##4\relax 325 | 1% 326 | \else 327 | \ifx\relax##2\relax 328 | \else 329 | \ifx\relax##4\relax 330 | \else 331 | \pdftexcmds@equalcont{##2}{##4}% 332 | \fi 333 | \fi 334 | \fi 335 | \fi 336 | }% 337 | \def\pdftexcmds@equalcont##1{% 338 | \def\pdftexcmds@equalcont####1####2##1##1##1##1{% 339 | ##1##1##1##1% 340 | \pdftexcmds@equal####1\delimiter####2\delimiter 341 | }% 342 | }% 343 | \expandafter\pdftexcmds@equalcont\csname fi\endcsname 344 | \else 345 | \long\def\pdf@isprimitive##1##2{% 346 | \ifnum\pdf@strcmp{\meaning##1}{\string##2}=0 % 347 | \expandafter\ltx@firstoftwo 348 | \else 349 | \expandafter\ltx@secondoftwo 350 | \fi 351 | }% 352 | \fi 353 | } 354 | \ifluatex 355 | \else 356 | \pdf@isprimitive 357 | \pdftexcmds@AtEnd 358 | \expandafter\endinput 359 | \fi 360 | \begingroup\expandafter\expandafter\expandafter\endgroup 361 | \expandafter\ifx\csname RequirePackage\endcsname\relax 362 | \input luatex-loader.sty\relax 363 | \else 364 | \RequirePackage{luatex-loader}[2009/04/10]% 365 | \fi 366 | \pdftexcmds@directlua{% 367 | require("oberdiek.pdftexcmds")% 368 | } 369 | \begingroup\expandafter\expandafter\expandafter\endgroup 370 | \expandafter\ifx\csname newtoks\endcsname\relax 371 | \toksdef\pdftexcmds@toks=0 % 372 | \else 373 | \csname newtoks\endcsname\pdftexcmds@toks 374 | \fi 375 | \ifnum\luatexversion<36 % 376 | \else 377 | \catcode`\0=9 % 378 | \fi 379 | \long\def\pdf@strcmp#1#2{% 380 | \directlua0{% 381 | oberdiek.pdftexcmds.strcmp("\luaescapestring{#1}",% 382 | "\luaescapestring{#2}")% 383 | }% 384 | }% 385 | \pdf@isprimitive 386 | \long\def\pdf@escapehex#1{% 387 | \directlua0{% 388 | oberdiek.pdftexcmds.escapehex("\luaescapestring{#1}", "byte")% 389 | }% 390 | }% 391 | \long\def\pdf@escapehexnative#1{% 392 | \directlua0{% 393 | oberdiek.pdftexcmds.escapehex("\luaescapestring{#1}")% 394 | }% 395 | }% 396 | \def\pdf@unescapehex#1{% 397 | \the\expandafter\pdftexcmds@toks 398 | \directlua0{% 399 | oberdiek.pdftexcmds.toks="pdftexcmds@toks"% 400 | oberdiek.pdftexcmds.unescapehex("\luaescapestring{#1}", "byte")% 401 | }% 402 | }% 403 | \def\pdf@unescapehexnative#1{% 404 | \the\expandafter\pdftexcmds@toks 405 | \directlua0{% 406 | oberdiek.pdftexcmds.toks="pdftexcmds@toks"% 407 | oberdiek.pdftexcmds.unescapehex("\luaescapestring{#1}")% 408 | }% 409 | }% 410 | \long\def\pdf@escapestring#1{% 411 | \directlua0{% 412 | oberdiek.pdftexcmds.escapestring("\luaescapestring{#1}", "byte")% 413 | }% 414 | } 415 | \long\def\pdf@escapename#1{% 416 | \directlua0{% 417 | oberdiek.pdftexcmds.escapename("\luaescapestring{#1}", "byte")% 418 | }% 419 | } 420 | \long\def\pdf@escapenamenative#1{% 421 | \directlua0{% 422 | oberdiek.pdftexcmds.escapename("\luaescapestring{#1}")% 423 | }% 424 | } 425 | \def\pdf@filesize#1{% 426 | \directlua0{% 427 | oberdiek.pdftexcmds.filesize("\luaescapestring{#1}")% 428 | }% 429 | } 430 | \def\pdf@filemoddate#1{% 431 | \directlua0{% 432 | oberdiek.pdftexcmds.filemoddate("\luaescapestring{#1}")% 433 | }% 434 | } 435 | \def\pdf@filedump#1#2#3{% 436 | \directlua0{% 437 | oberdiek.pdftexcmds.filedump("\luaescapestring{\number#1}",% 438 | "\luaescapestring{\number#2}",% 439 | "\luaescapestring{#3}")% 440 | }% 441 | }% 442 | \long\def\pdf@mdfivesum#1{% 443 | \directlua0{% 444 | oberdiek.pdftexcmds.mdfivesum("\luaescapestring{#1}", "byte")% 445 | }% 446 | }% 447 | \long\def\pdf@mdfivesumnative#1{% 448 | \directlua0{% 449 | oberdiek.pdftexcmds.mdfivesum("\luaescapestring{#1}")% 450 | }% 451 | }% 452 | \def\pdf@filemdfivesum#1{% 453 | \directlua0{% 454 | oberdiek.pdftexcmds.filemdfivesum("\luaescapestring{#1}")% 455 | }% 456 | }% 457 | \def\pdf@shellescape{% 458 | \directlua0{% 459 | oberdiek.pdftexcmds.shellescape()% 460 | }% 461 | } 462 | \def\pdf@system#1{% 463 | \directlua0{% 464 | oberdiek.pdftexcmds.system("\luaescapestring{#1}")% 465 | }% 466 | } 467 | \def\pdf@lastsystemstatus{% 468 | \directlua0{% 469 | oberdiek.pdftexcmds.lastsystemstatus()% 470 | }% 471 | } 472 | \def\pdf@lastsystemexit{% 473 | \directlua0{% 474 | oberdiek.pdftexcmds.lastsystemexit()% 475 | }% 476 | } 477 | \catcode`\0=12 % 478 | \ifnum0% 479 | \pdftexcmds@directlua{% 480 | if io.popen then % 481 | tex.write("1")% 482 | end% 483 | }% 484 | =1 % 485 | \def\pdf@pipe#1{% 486 | \the\expandafter\pdftexcmds@toks 487 | \pdftexcmds@directlua{% 488 | oberdiek.pdftexcmds.toks="pdftexcmds@toks"% 489 | oberdiek.pdftexcmds.pipe("\luaescapestring{#1}")% 490 | }% 491 | }% 492 | \fi 493 | \pdftexcmds@AtEnd 494 | \endinput 495 | %% 496 | %% End of file `pdftexcmds.sty'. 497 | -------------------------------------------------------------------------------- /articles/sty/reviewmacro.sty: -------------------------------------------------------------------------------- 1 | %% from review-pdfmaker 2 | \usepackage{fancyhdr} 3 | \pagestyle{fancy} 4 | \lhead{\gtfamily\sffamily\bfseries\upshape \leftmark} 5 | \chead{} 6 | \rhead{\gtfamily\sffamily\bfseries\upshape \rightmark} 7 | \renewcommand{\sectionmark}[1]{\markright{\thesection~#1}{}} 8 | \renewcommand{\chaptermark}[1]{\markboth{\prechaptername\ \thechapter\ \postchaptername~#1}{}} 9 | \renewcommand{\headfont}{\gtfamily\sffamily\bfseries} 10 | 11 | \fancypagestyle{plainhead}{% 12 | \fancyhead{} 13 | \fancyfoot{} % clear all header and footer fields 14 | \fancyfoot[CE,CO]{\thepage} 15 | \renewcommand{\headrulewidth}{0pt} 16 | \renewcommand{\footrulewidth}{0pt}} 17 | 18 | %% using Helvetica as sans-serif 19 | \renewcommand{\sfdefault}{phv} 20 | 21 | \sloppy 22 | 23 | %% del tag strike 24 | \usepackage{ulem} 25 | \renewcommand{\reviewstrike}[1]{\sout{#1}} 26 | 27 | -------------------------------------------------------------------------------- /articles/sty/samplemacro.sty: -------------------------------------------------------------------------------- 1 | %% from review-pdfmaker 2 | \usepackage{fancyhdr} 3 | \usepackage{ulem} 4 | \pagestyle{fancy} 5 | 6 | \fancyhead{} 7 | \fancyhead[LE]{\gtfamily\sffamily\bfseries\upshape \leftmark} 8 | \fancyhead[RO]{\gtfamily\sffamily\bfseries\upshape \rightmark} 9 | \cfoot{\thepage} 10 | 11 | \renewcommand{\sectionmark}[1]{\markright{\thesection~#1}{}} 12 | \renewcommand{\chaptermark}[1]{\markboth{\prechaptername\ \thechapter\ \postchaptername~#1}{}} 13 | \renewcommand{\headfont}{\gtfamily\sffamily\bfseries} 14 | 15 | \fancypagestyle{plainhead}{% 16 | \fancyhead{} 17 | \fancyfoot{} % clear all header and footer fields 18 | \fancyfoot[CE,CO]{\thepage} 19 | \renewcommand{\headrulewidth}{0pt} 20 | \renewcommand{\footrulewidth}{0pt}} 21 | 22 | \hypersetup{colorlinks=false} 23 | %%Helveticaを使う 24 | \renewcommand{\sfdefault}{phv} 25 | 26 | \sloppy 27 | 28 | \cfoot{\thepage} 29 | 30 | \def\cleardoublepage{% 31 | \clearpage% 32 | \if@twoside% 33 | \ifodd \c@page \else \hbox{}\thispagestyle{plainhead}\newpage% 34 | \if@twocolumn\hbox{}\thispagestyle{plainhead}\newpage\fi% 35 | \fi% 36 | \fi% 37 | } -------------------------------------------------------------------------------- /articles/sty/tatsumacro.sty: -------------------------------------------------------------------------------- 1 | \geometry{top=14mm,bottom=16mm,left=5mm,right=5mm} 2 | 3 | \definecolor{borderblue}{rgb}{0.2,0.4,0.9} 4 | 5 | \hypersetup{% 6 | colorlinks,% 7 | %%urlcolor=borderblue, 8 | linkcolor=borderblue% 9 | } 10 | 11 | %% from review-pdfmaker 12 | \usepackage{fancyhdr} 13 | \usepackage{ulem} 14 | %%\usepackage{supertabular} 15 | \setlength{\headheight}{29pt} 16 | \pagestyle{fancy} 17 | \lhead{\gtfamily\sffamily\bfseries\upshape \leftmark} 18 | \chead{} 19 | \rhead{} 20 | \cfoot{} 21 | \rfoot{\thepage} 22 | \renewcommand{\sectionmark}[1]{\markright{\thesection~#1}{}} 23 | \renewcommand{\chaptermark}[1]{% 24 | \if@mainmatter 25 | \markboth{\prechaptername\ \thechapter\ \postchaptername~#1}{} 26 | \else 27 | \markboth{#1}{#1} 28 | \fi} 29 | \renewcommand{\headfont}{\gtfamily\sffamily\bfseries} 30 | 31 | \fancypagestyle{plainhead}{% 32 | \fancyhead{} 33 | \fancyfoot{} % clear all header and footer fields 34 | \fancyfoot[R]{\thepage} 35 | \renewcommand{\headrulewidth}{0pt} 36 | \renewcommand{\footrulewidth}{0pt}} 37 | 38 | 39 | \renewcommand{\reviewtitlefont}[0]{% 40 | \usefont{T1}{pag}{b}{n}\gtfamily\ebseries} 41 | \renewcommand{\reviewmainfont}[0]{% 42 | \usefont{T1}{phv}{m}{n}\mgfamily} 43 | 44 | \renewcommand{\textbf}[1]{% 45 | {\usefont{T1}{phv}{b}{n}\gtfamily\bfseries~#1}} 46 | 47 | %%URLのフォントを通常のフォントにする 48 | \urlstyle{same} 49 | 50 | %%Helveticaを使う 51 | \renewcommand{\sfdefault}{phv} 52 | 53 | %%全体のデフォルトを丸ゴシック+Helveticaに 54 | \renewcommand{\kanjifamilydefault}{\mgdefault} 55 | \renewcommand{\familydefault}{\sfdefault} 56 | 57 | %%色修正 58 | \definecolor{shadecolorb}{gray}{0.5} 59 | 60 | %%シングルクォート対応 61 | \usepackage{upquote,textcomp} 62 | \xspcode`'=0 63 | 64 | %\renewcommand{\reviewcolumnhead}[2]{% 65 | %{\noindent\large ■#2}} 66 | 67 | %%framed.sty 68 | %%//cmdや//emlistの横幅をぎりぎりまで広げる 69 | \setlength{\FrameSep}{4pt} 70 | 71 | %%table 72 | \renewenvironment{reviewtable}[1]{% 73 | \begin{center}\scriptsize\setlength{\baselineskip}{1.5zw} 74 | \renewcommand\arraystretch{1.5} 75 | \begin{tabular}{#1}}{% 76 | \end{tabular} 77 | \end{center}} 78 | 79 | \renewcommand{\reviewappendix}{% 80 | \backmatter% 81 | \fancyhead{} 82 | } 83 | 84 | \def\@makechapterhead#1{% 85 | \vspace*{0.5\Cvs}% 欧文は50pt 86 | {\parindent \z@ \raggedright \normalfont 87 | \ifnum \c@secnumdepth >\m@ne 88 | \if@mainmatter 89 | \hfill{\huge\headfont \@chapapp\thechapter\@chappos} 90 | \par\nobreak 91 | \vskip 0.5\Cvs % 欧文は20pt 92 | \fi 93 | \fi 94 | \interlinepenalty\@M 95 | \hfill{\huge \headfont #1\par\nobreak} 96 | \vspace*{-2mm}% 97 | \rule{\textwidth}{0.1mm} 98 | \vskip 1.0\Cvs}} % 欧文は40pt 99 | 100 | 101 | 102 | \makeatletter%% 103 | 104 | \long\def\@makecaption#1#2{{\small 105 | \advance\leftskip .0628\linewidth 106 | \advance\rightskip .0628\linewidth 107 | \vskip\abovecaptionskip 108 | \sbox\@tempboxa{\textgt{\textsf{#1}}\hskip1zw\relax \textgt{\textsf{#2}}}% 109 | \ifdim \wd\@tempboxa <\hsize \centering \fi 110 | \textgt{\textsf{#1}}\hskip1zw\relax \textgt{\textsf{#2}}\par 111 | \vskip\belowcaptionskip}} 112 | 113 | \makeatother%% プリアンブルで定義する場合は必須(文字コードも) 114 | 115 | \sloppy 116 | 117 | %%目次と本文でページ番号を振り直しても表紙・中表紙ページと重複しないようRomanにしておく 118 | \pagenumbering{Roman} 119 | 120 | -------------------------------------------------------------------------------- /articles/sty/techbooster-doujin.sty: -------------------------------------------------------------------------------- 1 | \usepackage[most]{tcolorbox} 2 | \tcbuselibrary{breakable} 3 | \renewenvironment{shaded}{ 4 | \vspace{\baselineskip} 5 | \begin{tcolorbox}[breakable, enhanced jigsaw, colback=black!10!white, colframe=black!30!white]} 6 | {\end{tcolorbox}} 7 | \renewenvironment{shadedb}{ 8 | \vspace{\baselineskip} 9 | \begin{tcolorbox}[breakable, enhanced jigsaw, colback=black!80!white, colframe=black, colupper=white, sharp corners, rounded corners=southeast]} 10 | {\end{tcolorbox}} 11 | 12 | %% サンプルコードを更に小さく 13 | \renewenvironment{reviewemlist}{% 14 | \medskip\footnotesize\begin{shaded}\setlength{\baselineskip}{1.2zw}\begin{alltt}}{% 15 | \end{alltt}\end{shaded}} 16 | 17 | \renewenvironment{reviewlist}{% 18 | \begin{shaded}\footnotesize\setlength{\baselineskip}{1.2zw}\begin{alltt}}{% 19 | \end{alltt}\end{shaded}\par\vspace*{0.5zw}} 20 | 21 | \renewenvironment{reviewcmd}{% 22 | \color{white}\medskip\footnotesize\begin{shadedb}\setlength{\baselineskip}{1.2zw}\begin{alltt}}{% 23 | \end{alltt}\end{shadedb}} 24 | 25 | %% from review-pdfmaker 26 | \usepackage{fancyhdr} 27 | \usepackage{ulem} 28 | \pagestyle{fancy} 29 | 30 | \fancyhead{} 31 | \fancyhead[LE]{\gtfamily\sffamily\bfseries\upshape \leftmark} 32 | \fancyhead[RO]{\gtfamily\sffamily\bfseries\upshape \rightmark} 33 | \cfoot{\thepage} 34 | 35 | \renewcommand{\sectionmark}[1]{\markright{\thesection~#1}{}} 36 | \renewcommand{\chaptermark}[1]{% 37 | \if@mainmatter 38 | \markboth{\prechaptername\ \thechapter\ \postchaptername~#1}{} 39 | \else 40 | \markboth{#1}{#1} 41 | \fi} 42 | \renewcommand{\headfont}{\gtfamily\sffamily\bfseries} 43 | 44 | \fancypagestyle{plainhead}{% 45 | \fancyhead{} 46 | \fancyfoot{} % clear all header and footer fields 47 | \fancyfoot[CE,CO]{\thepage} 48 | \renewcommand{\headrulewidth}{0pt} 49 | \renewcommand{\footrulewidth}{0pt}} 50 | 51 | \hypersetup{colorlinks=false} 52 | %%Helveticaを使う 53 | \renewcommand{\sfdefault}{phv} 54 | 55 | \sloppy 56 | 57 | \cfoot{\thepage} 58 | 59 | \def\cleardoublepage{% 60 | \clearpage% 61 | \if@twoside% 62 | \ifodd \c@page \else \hbox{}\thispagestyle{plainhead}\newpage% 63 | \if@twocolumn\hbox{}\thispagestyle{plainhead}\newpage\fi% 64 | \fi% 65 | \fi% 66 | } 67 | 68 | \setlength{\paperheight} {257 truemm} 69 | \setlength{\paperwidth} {182 truemm} 70 | \setlength{\textheight} {190 truemm} 71 | \setlength{\textwidth} {130 truemm} 72 | \setlength{\marginparwidth} {15 truemm} 73 | \setlength{\marginparsep} {2 truemm} 74 | \setlength{\oddsidemargin} {26 truemm} 75 | \setlength{\evensidemargin} {26 truemm} 76 | \setlength{\topmargin} {10 truemm} 77 | \setlength{\headsep} {15 truemm} 78 | \setlength{\headheight} {5 truemm} 79 | \setlength{\hoffset}{-1in} 80 | \setlength{\voffset}{-1in} 81 | \renewcommand{\baselinestretch}{0.96} 82 | 83 | \usepackage{fancyvrb} 84 | \VerbatimFootnotes 85 | \usepackage{seqsplit} 86 | \let\textttorg=\texttt 87 | \def\texttt{\begingroup\obeyspaces\do@texttt} 88 | \def\do@texttt#1{\textttorg{\seqsplit{#1\relax}}\endgroup} 89 | 90 | \newcommand{\captionsize}{\fontsize{9}{9}\selectfont} 91 | \newlength{\captionnumwidth} 92 | \setlength{\captionnumwidth}{6zw} 93 | \newlength{\captionwidth} 94 | \setlength{\captionwidth}{\textwidth} 95 | \addtolength{\captionwidth}{-\captionnumwidth} 96 | \def\captionhead{\sffamily{\color{black!30!white}{▲}}} 97 | \long\def\@makecaption#1#2{% 98 | \addvspace\abovecaptionskip 99 | \buildcaption{!}{#1}{#2} 100 | \vskip\belowcaptionskip 101 | } 102 | \long\def\buildcaption#1#2#3{% 103 | \sbox\@tempboxa{\captionsize\sffamily #3}% 104 | \ifdim \wd\@tempboxa <\captionwidth% 105 | \def\capline{\captionsize\captionhead #2% 106 | \hskip1zw\relax\usebox{\@tempboxa}\mbox{}\relax} 107 | \ifx#1!\relax\centerline{\capline}\else\noindent\capline\fi 108 | \else 109 | \noindent% 110 | \parbox[t]{\captionnumwidth}{% 111 | {\captionsize\captionhead #2% 112 | \hskip1zw}}% 113 | \parbox[t]{\captionwidth}{\captionsize\sffamily #3} 114 | \fi 115 | } 116 | 117 | \long\def\listcaption#1:#2\relax{\buildcaption{}{#1}{#2}} 118 | 119 | \renewcommand{\reviewtablecaption}[1]{% 120 | {\def\captionhead{\sffamily{\color{black!30!white}{▼}}} 121 | \caption{#1}}} 122 | 123 | \renewcommand{\reviewlistcaption}[1]{% 124 | {\def\captionhead{\sffamily{\color{black!30!white}{▼}}} 125 | \medskip{\listcaption#1\relax}\vspace*{-1.3zw}}} 126 | 127 | \renewcommand{\reviewemlistcaption}[1]{% 128 | {\def\captionhead{\sffamily{\color{black!30!white}{▼}}} 129 | \medskip{\buildcaption{}{}{#1}\relax}\vspace*{-1.3zw}}} 130 | 131 | \renewcommand{\reviewcmdcaption}[1]{% 132 | {\def\captionhead{\sffamily{\color{black!30!white}{▼}}} 133 | \medskip{\listcaption#1\relax}\vspace*{-1.3zw}}} 134 | 135 | \renewcommand{\reviewindepimagecaption}[1]{% 136 | \begin{center}#1\end{center}} 137 | 138 | \renewcommand{\reviewboxcaption}[1]{% 139 | {\def\captionhead{\sffamily{\color{black!30!white}{▼}}} 140 | \medskip{\captionsize\captionhead #1}\vspace*{-1.3zw}}} 141 | 142 | 143 | 144 | \renewenvironment{reviewcolumn}{% 145 | \vspace{\baselineskip} 146 | \begin{tcolorbox}[colback=white] 147 | }{% 148 | \end{tcolorbox} 149 | \vspace{\baselineskip} 150 | } 151 | 152 | \renewcommand{\reviewcolumnhead}[2]{% 153 | {\noindent\large\sffamily #2} 154 | \vspace{\baselineskip}} 155 | 156 | %%シングルクォート対応 157 | \usepackage{upquote,textcomp} 158 | \xspcode`'=0 159 | -------------------------------------------------------------------------------- /articles/sty/ulem.sty: -------------------------------------------------------------------------------- 1 | % 2 | % U L E M . S T Y [2012-05-18] 3 | % 4 | % The ulem package provides various types of underlining that can stretch 5 | % between words and be broken across lines in LaTeX or plain TeX. 6 | % In LaTeX ulem replaces italics with underlining in \em-phasized text. 7 | % It is most suitable for simple text such as {\em ibid.} or \emph{\LaTeX: 8 | % A Document Preparation System} that may need to be underlined in a 9 | % manuscript submitted for publication. A declaration of \normalem (or 10 | % the \usepackage option "normalem") restores the normal \em behavior. 11 | % 12 | % Full instructions appear in ulem.ltx (ulem.pdf). In summary: 13 | % 14 | % \uline{important} underlined text 15 | % \uuline{urgent} double-underlined text 16 | % \uwave{boat} wavy underline 17 | % \sout{wrong} line drawn through word 18 | % \xout{removed} marked over with //////. 19 | % \dashuline{dashing} dash underline 20 | % \dotuline{dotty} dotted underline 21 | % 22 | % {\em phasized\/} | In LaTeX, by default, these are underlined; use 23 | % \emph{asized} | \normalem or [normalem] to restore italics 24 | % \useunder{\uwave}{\bfseries}{\textbf} 25 | % use wavy underline in place of bold face 26 | % Use \markoverwith for defining new types of underlining. 27 | % 28 | % Copyright (c) 1989-2011 by Donald Arseneau (Vancouver, Canada; asnd@triumf.ca) 29 | % 30 | % This software may be freely transmitted, reproduced, or modified for any 31 | % purpose provided that this copyright notice is left intact. 32 | % (Small excerpts may be taken and used without any restriction.) 33 | % 34 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 35 | % Defend against multiple loading. 36 | \expandafter \ifx \csname UL@box\endcsname \relax \else 37 | \immediate\write16{ulem.sty refuses to load twice. }\endinput \fi 38 | 39 | % Set catcode of @ in case it isn't a "letter" already 40 | \chardef\ULthickness\catcode\string`\@ % hold catcode temporarily 41 | \catcode\string`\@=11 42 | 43 | \def\uline{\relax \ifmmode\expandafter\underline 44 | \else \bgroup\expandafter\ULset\fi} 45 | 46 | \newbox\UL@box 47 | \newbox\UL@hyphenbox 48 | \newskip\UL@skip 49 | \newtoks\UL@hook 50 | 51 | \newdimen\UL@height 52 | 53 | \newcount\UL@pe 54 | \let\LA@space\ \let\LA@hskip\hskip 55 | 56 | \def\UL@end *{\relax\relax}% something harmless unlikely to be found elsewhere 57 | 58 | % For regular underlines, set the depth based on the font, or retain 59 | % the preset value, then start underlining. 60 | \def\ULset{\UL@setULdepth 61 | \def\UL@leadtype{\leaders \hrule \@height\UL@height \@depth\ULdepth}% 62 | \ifmmode \ULdepth-4\p@ \fi 63 | \UL@height-\ULdepth \advance\UL@height\ULthickness \ULon} 64 | 65 | % Automatically set \ULdepth if it is to be automatic (flagged by \maxdimen) 66 | \def\UL@setULdepth{\relax 67 | \ifdim\ULdepth=\maxdimen % Set depth based on font, if not set already 68 | \setbox\UL@box\hbox{{(j}}\ULdepth\dp\UL@box\advance\ULdepth.4\p@ 69 | % use setbox to support plain TeX 70 | \fi} 71 | 72 | % \ULon simply calls \UL@on (possibly \UL@on=\UL@onin) for text mode, but 73 | % \UL@onmath if it is math mode. 74 | \def\ULon{\ifmmode \expandafter\UL@onmath\else \expandafter\UL@on\fi} 75 | 76 | % \UL@on sets the engine of underline running, and tells it 77 | % where to stop. 78 | \long\def\UL@on#1{\leavevmode\UL@ender \let\UL@on\UL@onin 79 | \everymath{\UL@hrest}\everyvbox{\UL@hrest}\let\hskip\UL@hskip 80 | \let\\\UL@cr \let\-\UL@dischyp \let\newline\UL@newline \let\ \UL@space 81 | \def\hfil{\hskip\z@ plus1fil\relax}\def\hfill{\hskip\z@ plus1fill\relax}% 82 | \def\hss{\hskip\z@ plus1filminus1fil\relax}\let\penalty\UL@penalty 83 | \the\UL@hook 84 | \UL@word\@empty#1\xdef\UL@spfactor{\the\spacefactor} \UL@end * } 85 | 86 | % This is what \ULon does when it appears nested in an inner place. 87 | \def\UL@onin#1{\leavevmode\UL@ender % when nested, do multiple underlining 88 | \UL@height\ULthickness \advance\ULdepth\thr@@\UL@height 89 | \advance\UL@height-\ULdepth \setbox\UL@box\hbox{{#1}}% 90 | \let\UL@start\relax\UL@putbox\egroup} 91 | % \UL@putbox is disabled in inner mode, so re-enable it by changing \UL@start 92 | % \UL@hrest is implicit due to \everyhbox. Double braces for \hbox are in 93 | % lieu of \color@begin(end)group. 94 | 95 | % This is what \ULon does in math mode. 96 | \def\UL@onmath#1{\UL@ender\mathord{\UL@hrest\mathop{\kern\z@#1}\limits\sb 97 | {\UL@leadtype\LA@hskip\p@ plus1fill}}\egroup} 98 | 99 | \def\UL@unegroup{} 100 | \gdef\UL@ender{} 101 | % end-brace matching hack for when command is used as a font declaration: 102 | \def\UL@swender{\ifnum`{=\z@\fi\aftergroup}\gdef\UL@ender{}} 103 | 104 | % must expand to nothing outside the ifs for syntactical spaces to work. 105 | % the \expandafters get rid of the \@empty inserted at the beg. of word 106 | \def\UL@word#1 {\expandafter\UL@start#1 % 107 | \expandafter\ifx\expandafter\UL@end#1\egroup\egroup 108 | \unskip \unskip \unskip % remove extra leader at end 109 | \spacefactor\UL@spfactor \let\UL@word\egroup 110 | \else % not finished 111 | \ifmmode\else \ifdim\lastskip=\z@\else % allow syntactical spaces 112 | \global\UL@skip\lastskip \unskip 113 | \UL@stop \UL@leaders 114 | \fi\fi 115 | \fi \UL@word\@empty}% \@empty preserves braces in param 116 | 117 | % \UL@start: start of each chunk. It gives two levels of grouping. 118 | % Each chunk is ended by \UL@stop. Local intermissions go like 119 | % \UL@stop...\UL@start. 120 | \def\UL@start{\setbox\UL@box\hbox\bgroup\everyhbox{\UL@hrest}% 121 | % the following are to cope with stops (\ ,\- etc) within extra braces 122 | \let\UL@start\@empty \def\UL@unegroup{\bgroup\bgroup}\let\UL@leadtype\@empty 123 | \bgroup \kern-3sp\kern3sp % kerns so I can test for beginning of list 124 | \if@ignore \global\@ignorefalse \ignorespaces \fi} 125 | 126 | \def\UL@stop{\global\UL@pe\lastpenalty \unpenalty % penalty in \UL@pe 127 | \ifnum\lastkern=\thr@@ \egroup\egroup % Nothing in hbox...but make sure: 128 | \ifdim\wd\UL@box=\z@ \else \UL@putbox \fi % something in box so print it 129 | \else \egroup\egroup \UL@putbox % something in box so print it 130 | \fi \ifnum\UL@pe=\z@ \else \LA@penalty\UL@pe \fi % use penalty from inside box 131 | \UL@unegroup} 132 | % notice that a box with only a penalty in it is discarded, but the penalty 133 | % is still used! This is so a series of discardable glues and penalties 134 | % behaves properly. 135 | 136 | \def\UL@putbox{\ifx\UL@start\@empty \else % not inner 137 | \vrule\@width\z@ \LA@penalty\@M 138 | {\UL@skip\wd\UL@box \UL@leaders \kern-\UL@skip}% 139 | \box\UL@box \fi} 140 | 141 | % With interword leaders, give some overlap to avoid gaps caused by 142 | % round-off errors in the printing program. Needs \unskip \unskip \unskip 143 | % above. This version overlaps 1/300 inch, which looks good at high 144 | % resolution, and will still work down to ~150 dpi. Change the value 145 | % of \UL@pixel if necessary. 146 | 147 | \newdimen\UL@pixel \UL@pixel=1in \divide\UL@pixel 300 148 | 149 | \def\UL@leaders{{\LA@hskip-\UL@pixel \advance\UL@skip\tw@\UL@pixel 150 | \UL@leadtype\LA@hskip\UL@skip \LA@hskip-\UL@pixel}} 151 | 152 | % restore some things for inside math or \mbox 153 | \def\UL@hrest{\let\ \LA@space \let\-\@empty \let\penalty\LA@penalty} 154 | 155 | \def\UL@space{\LA@space \global\UL@skip\lastskip \unskip \UL@reskip}% \ 156 | 157 | % Hyphenation is done by explicit \discretionary. The overlapping melds 158 | % with the running overlap because it *is* part of the running overlap: 159 | % The word fragment is extended by the width of the hyphenation which is 160 | % then overlapped by leaders. The discretionary may occupy this space 161 | % if a break occurs; otherwise the next syllable gets doubly-overlapped 162 | % (in registration) for a distance of the hyphen's width. 163 | \def\UL@dischyp{\global\setbox\UL@hyphenbox\hbox 164 | {\ifnum \hyphenchar\font<\z@ \string-\else \char\hyphenchar\font \fi}% 165 | \kern\wd\UL@hyphenbox \LA@penalty\@M 166 | \UL@stop \kern-\wd\UL@hyphenbox 167 | \discretionary{\box\UL@hyphenbox}{}{}\UL@start} 168 | 169 | \let\LA@penalty\penalty 170 | \def\UL@penalty{\relax\ifhmode \afterassignment\UL@@penalty\count@ 171 | \else\LA@penalty\fi} 172 | \def\UL@@penalty{\LA@penalty \ifnum\count@=\z@ 173 | \@ne \else \count@ \fi % zero penalty => no penalty, so use 1 instead. 174 | \UL@stop \UL@start} 175 | 176 | % The test \ifx\ \LA@space \else means we are neither in math mode nor an 177 | % \mbox, so it is safe to stop the current \UL@box. \ , \- , and \penalty 178 | % (= \linebreak or \nolinebreak) are common enough that they are restored 179 | % directly (by \UL@hrest); \\, \newline, \hskip (= \hspace) are rare enough 180 | % that the test is incorporated in their UL versions. This adds processing 181 | % when they're used, but saves processing in \UL@hrest called by \everymath 182 | % \everyvbox and \everyhbox. 183 | 184 | \def\UL@hskip{\ifx\ \LA@space \LA@hskip \else 185 | \afterassignment\UL@reskip \global\UL@skip \fi} 186 | 187 | \def\UL@reskip{\UL@stop \UL@leaders \UL@start} 188 | 189 | % Redefine \\ and \newline so the vertical space from \\[ ] is not lost 190 | % and so the \hfil is not underlined! \\ and \newline do nothing if inside 191 | % inner braces. 192 | 193 | \def\UL@cr{\unskip \ifx\ \LA@space \let\UL@vad\@gobble 194 | \else \UL@stop \unskip\unskip\unskip \let\UL@vad\vadjust \fi 195 | \@ifstar{\UL@vad{\LA@penalty\@M}\UL@cra}\UL@cra} 196 | \def\UL@cra{\@ifnextchar[\UL@crb\UL@newline} 197 | \def\UL@crb[#1]{\UL@vad{\vskip#1}\UL@newline} 198 | 199 | \def\UL@newline{\ifx\UL@start\@empty % (\UL@cr may have \UL@stop-ed already) 200 | \unskip \ifx\ \LA@space \else \UL@stop \unskip\unskip\unskip \fi\fi 201 | \LA@hskip \z@\@plus.0001fil\LA@penalty -\@M \UL@start} 202 | 203 | % That concludes the basic underlining. To put various other objects 204 | % (characters) under (or over) text we need to define \markoverwith 205 | % to set the overlay material in a box, and use leaders of that box for 206 | % overlaying the text. Here, the meaning of \UL@pixel is changed so 207 | % that `pixel' size = box size. Note that we generally need \leaders 208 | % (not \cleaders) for text, because an underline will be a patchwork 209 | % of small \leaders, and the characters must stay in registration. 210 | % However, we "hook" the leaders command so specific applications can 211 | % reassign it (\let\ULleaders\xleaders or \let\ULleaders\cleaders). 212 | % 213 | \newbox\ULC@box 214 | \let\ULleaders\leaders 215 | 216 | \def\markoverwith#1{\setbox\ULC@box\hbox{{#1}}\UL@pixel.5\wd\ULC@box 217 | \ifmmode \setbox\ULC@box\hbox{\raise1.4ex\box\ULC@box}% 218 | \dp\ULC@box-1.4ex\ht\ULC@box\z@ \def\UL@leadtype{\cleaders\copy\ULC@box}% 219 | \else 220 | \def\UL@leadtype{\ULleaders\copy\ULC@box}% 221 | \fi} 222 | 223 | % Now define various special underlines. All the definitions go like 224 | % \def \command {\bgroup \markoverwith{something} \ULon} 225 | 226 | % For drawing a wavey underline instead of a straight one the command 227 | % is \uwave (under-wave) which uses the wiggle from 6-pt lasy font: 228 | 229 | \def\uwave{\bgroup \markoverwith{\lower3.5\p@\hbox{\sixly \char58}}\ULon} 230 | \font\sixly=lasy6 % does not re-load if already loaded, so no memory problem. 231 | 232 | % To draw a double underline under text, use \uuline{text} 233 | 234 | \def\uuline{\bgroup \UL@setULdepth 235 | \markoverwith{\lower\ULdepth\hbox 236 | {\kern-.03em\vbox{\hrule width.2em\kern1.2\p@\hrule}\kern-.03em}}% 237 | \ULon} 238 | 239 | % To draw a line through text instead of under it (strike out) do 240 | % `under'-line with negative depth. Note that this one uses a real 241 | % line, not characters, so there is no \markoverwith. 242 | 243 | \def\sout{\bgroup \ULdepth=-.55ex \ULset} 244 | 245 | % To mark //// over text instead of underlining (x-out) 246 | % 247 | \def\xout{\bgroup \markoverwith{\hbox to.35em{\hss/\hss}}\ULon} 248 | 249 | \def\dotuline{\bgroup 250 | \UL@setULdepth 251 | \markoverwith{\begingroup 252 | \advance\ULdepth0.08ex 253 | \lower\ULdepth\hbox{\kern.1em .\kern.04em}% 254 | \endgroup}% 255 | \ULon} 256 | 257 | \def\dashuline{\bgroup 258 | \UL@setULdepth 259 | \markoverwith{\kern.13em 260 | \vtop{\kern\ULdepth \hrule width .3em}% 261 | \kern.13em}\ULon} 262 | 263 | % A command to declare that an underline command should be used in 264 | % place of a particular font selection: 265 | % \useunder {underline_command}{font_declaration}{font_command} 266 | % e.g.: \useunder{\uuline}{\bfseries}{\textbf} 267 | % \useunder{\uwave}{\bf}{} 268 | 269 | \def\useunder#1#2#3{\relax 270 | \ifx\relax#2\relax\else % declaration command given 271 | \def#2{\def\@tempa{#1}\global\let\UL@ender\UL@swender 272 | \expandafter\@tempa\expandafter{\ifnum\z@=\string`}\fi}% 273 | \MakeRobust{#2}\fi 274 | \ifx\relax#3\relax\else % argumentative command 275 | \def#3{#1}\MakeRobust{#3}\fi} 276 | 277 | \expandafter\ifx \csname @ifundefined\endcsname \relax 278 | 279 | % Allow plain TeX to use ulem.sty: 280 | \def\@height{height} 281 | \def\@depth{depth} 282 | \def\@width{width} 283 | \def\@empty{} 284 | \long\def\@gobble#1{} 285 | \def\MakeRobust#1{} 286 | % Do non-outer \newif with no visible \if's or \fi's when skipping 287 | \csname newif\expandafter\endcsname \csname if@ignore\endcsname 288 | 289 | \else 290 | 291 | \def\MakeRobust#1{\expandafter\let 292 | \csname \expandafter\@gobble\string#1 \endcsname= #1% 293 | \edef#1{\noexpand\protect \expandafter\noexpand 294 | \csname\expandafter\@gobble\string#1 \endcsname} 295 | } 296 | \MakeRobust\uline 297 | \MakeRobust\uuline 298 | \MakeRobust\uwave 299 | \MakeRobust\sout 300 | \MakeRobust\xout 301 | 302 | \let\LA@em\em \let\LA@emph\emph 303 | \expandafter\let\expandafter\LA@Pem \csname em \endcsname 304 | \expandafter\let\expandafter\LA@Pemph \csname emph \endcsname 305 | \def\ULforem{\useunder{\uline}{\em}{\emph}} 306 | \def\normalem{\let\em\LA@em \let\emph\LA@emph 307 | \expandafter\let\csname em \endcsname\LA@Pem 308 | \expandafter\let\csname emph \endcsname\LA@Pemph} 309 | \ULforem % default is to use underlining for \em, 310 | 311 | \fi 312 | 313 | % Process LaTeX \package options; plain TeX skips this section 314 | 315 | \expandafter\ifx\csname ProvidesPackage\endcsname \relax \else 316 | \ProvidesPackage{ulem}[2012/05/18] 317 | \DeclareOption{normalem}{\normalem} 318 | \DeclareOption{ULforem}{\ULforem} 319 | \DeclareOption{normalbf}{} 320 | \DeclareOption{UWforbf}{\useunder{\uwave}{\bf}{\textbf}} 321 | \ProcessOptions 322 | % 323 | \AtBeginDocument{\let\UL@marginpar\marginpar}% 324 | \newcommand\UL@marpar[2][\gDefault@pt]{\gdef\gDefault@pt{#2}% 325 | \ifx\ \LA@space \@latexerr{Marginpar lost}% 326 | \else \UL@stop \UL@marginpar[#1]{#2}\UL@start \fi} 327 | \addto@hook\UL@hook{\let\marginpar\UL@marpar} 328 | \fi 329 | 330 | \catcode`@=\ULthickness % Scratch meaning: restore catcode of @ 331 | 332 | \def\ULthickness{.4pt}% can change this with \renewcommand 333 | \newdimen\ULdepth \ULdepth=\maxdimen 334 | % "maxdimen" depth causes the depth to be set according to the font. You 335 | % can change \ULdepth for a permanent setting or a special effect (\sout). 336 | 337 | \endinput 338 | 339 | %====================== BEGIN INSTRUCTIONS =========================== 340 | These plain text instructions may disappear soon. The primary instructions 341 | are in ulem.ltx (ulem.pdf, ulem.dvi). 342 | 343 | ULEM is a package for LaTeX or plain TeX which provides various types of 344 | underlining that can stretch between words and be broken across lines. 345 | In LaTeX this style replaces italics with underlining in emphasized text 346 | given by \em or \emph -- but only if the text is delimited by braces. A 347 | declaration \normalem (or the \usepackage option [normalem]) restores the 348 | normal \em behavior. For underlining in plain TeX, \input ulem.sty, and 349 | use the \uline command. 350 | 351 | Unlike regular underlining, ulem allows line breaks, and even primitive 352 | hyphenation, within the underlined text; but it is far from perfect. It is 353 | most suitable for simple text like {\em \LaTeX: A document preparation 354 | system\/} that may need to be underlined in a manuscript submitted for 355 | publication. Again, ulem will only replace \em and \emph when the text is 356 | delimited by explicit braces. 357 | 358 | The thickness of the underline rule is given by \ULthickness; use 359 | \renewcommand or \def (not \setlength) to change it. The depth of the 360 | underline is controlled by the length \ULdepth. The default value is a 361 | special flag which lets the current font control the depth. You can set 362 | a particular value to \ULdepth (using \setlength) to force a particular 363 | depth, either locally for a special purpose, or for the document as a 364 | whole. See the definition of \sout. 365 | 366 | Every word is typeset in an underlined box, so automatic hyphenation is 367 | normally disabled, but explicit discretionary hyphens (\-) will still be 368 | obeyed. Several text-formatting commands are specially supported within 369 | the underlining: \-, \ , ~, \\, \newline, \linebreak, \nolinebreak, 370 | \penalty, \hskip, \hspace, \hfil, \hfill, \hss. Displayed math and \par 371 | are deliberately not supported to aid in the detection of runaway arguments 372 | (missing braces). The special commands do have a problem: they end a 373 | group so any local assignments are lost. 374 | 375 | The underlines continue between words, and stretch just like ordinary 376 | spaces do. Since spaces delimit words, there may be some difficulty 377 | with syntactical spaces (e.g. "2.3 pt"). Some effort is made to handle 378 | such cases, but sometimes (such as \let\x= y) the space is interpreted 379 | incorrectly. You can usually solve the problem by enclosing the offending 380 | command in braces or in a macro (\newcommand\xeqy{\let\x= y}), but... 381 | 382 | One important incompatibility with braces and macro replacement: 383 | ALL THE TEXT IN BRACES OR COMING FROM A MACRO IS TYPESET IN A BOX. 384 | That is, braces will suppress stretching and linebreaks in the text they 385 | enclose. Moreover, the specially-taken-care-of commands \-, \\, \newline 386 | and \linebreak are usually ignored if they appear inside extra braces. 387 | They operate only when the braces delimit a command parameter without 388 | introducing a level of grouping. (Even though braces delimiting command 389 | parameters do not normally imply grouping, many commands will add their 390 | own grouping.) Thus, you should try to limit inner braces to short bits of 391 | text or for delimiting parameters to commands. For emergency repairs, see 392 | the "Marat/Sade" example below. Syntactical spaces inside braces never 393 | cause a problem, nor do spaces in math mode. 394 | 395 | Text produced by expansion of a command (macro) is boxed too, but \\, \ 396 | and \- still work properly in the expansion text: 397 | \newcommand\iff{if and only if} {\em \iff} 398 | does not allow any stretching or linebreaking between words, but 399 | \newcommand\iff{if\ and\ only\ if} {\em \iff} 400 | allows stretching and linebreaking. There is a problem though: the 401 | \ between words closes a group and any local assignments will be lost, 402 | in particular, font changes and color changes. 403 | 404 | This loss of local assignments will break some other standard commands, 405 | (e.g., \cite) which produce multiple `words' using local assignments. 406 | The way to protect such commands is to bury them in an \mbox: 407 | {\em every\-one agrees~\mbox{\cite{you,me}}.} 408 | 409 | Nested \em commands produce multiple underlining, but heed the warnings 410 | about braces above. To get italics without underlining, use \it. Nesting 411 | of other types of underline is also possible, but the `underlines' may 412 | overlap. 413 | 414 | HERE IS A SIMPLE EXAMPLE. 415 | 416 | \noindent 'Twas {\em brillig\/} and the {\em slithy~toves\/} 417 | did {\em gyre\/} and {\em gim\-ble\/} in the {\em wabe,\\[2pt] } 418 | All {\em mim\-sey\/} were the {\em boro\-goves\/} and 419 | the {\em mome raths outgrabe}. 420 | 421 | HERE IS A DIFFICULT EXAMPLE. 422 | 423 | \usepackage{ulem} 424 | \setlength\textwidth{3.3in} 425 | \begin{document} 426 | % \large 427 | No, I did {\em not} act in the movie {\em \emph{The} % <<<<<<< Nested 428 | \emph{Persecu}\-\emph{tion} \emph{and} \emph{Assassination} \emph{of} 429 | \emph{Jean-Paul} \emph{Marat}, as per\-formed by the Inmates 430 | of the Asylum of Charenton under the Direc\-tion of the 431 | Marquis de~Sade!} But I {\em did} see it. 432 | \end{document} 433 | 434 | In the nested emphasis, \emph had to be given for each word separately 435 | so the spaces between could stretch and break into lines. Even the 436 | discretionary hyphen (\-) in `Persecution' had to be outside the braces, 437 | but the hyphen in `Direction' was just fine because it was not in nested 438 | braces. The same applies to other special commands like \ and ~. Also, 439 | the spaces are printed with only a single underline because they are 440 | outside the nested \emph commands. This example really illustrates that 441 | ulem does not handle nested emphasis very well! Nevertheless, it is fine 442 | for simple things. 443 | 444 | Underlining can also be done according to \uline{this text}. To use this 445 | type of underlining, but have \em still produce italics, put the command 446 | \normalem in the preamble of the document or load ulem with 447 | \usepackage[normalem]{ulem}. 448 | 449 | Some variations on underlining are provided, including a wavey underline 450 | (\uwave{under-wave}), double underlines (\uuline{two lines under this}), 451 | dashed (\dashuline{dashes underneath}) or dotted (\dotuline{dots below}) 452 | underlines, 453 | a line through text rather than under it (\sout{strike out}), and text 454 | crossed-out with /////// (\xout{cross out, X out}). You can define your 455 | own styles following the examples provided. The definition should be 456 | something like: 457 | 458 | \newcommand\command{\bgroup \markoverwith{something}\ULon} 459 | 460 | The "something" can be as simple as a single character, or as complex as 461 | you can keep track of. 462 | 463 | The various underlining commands are essentially textual, and will not 464 | work quite the same in math mode. But since some font commands, like \bf, 465 | serve both for text and math, math mode is handled (in an approximate way). 466 | The performance in math mode is somewhat different from in text: there will 467 | be no line breaks or stretching in the underlined text, and the vertical 468 | positioning may not be right. The results should be best for \uline, 469 | \uwave, \uuline, and any other truly UNDER-line you define. 470 | 471 | Any type of underlining can be substituted for any font-selection command 472 | by issuing a proper \useunder declaration: 473 | 474 | \useunder{\underlinecommand}{\fontdeclaration}{\fontcommand} 475 | 476 | e.g., \useunder{\uuline}{\bfseries}{\textbf} gives a double underline 477 | instead of bold face in LaTeX. The commands \normalem and \ULforem switch 478 | underlining for \em off and on, respectively, and so do the \usepackage 479 | options [normalem] and [ULforem]. There is also the \usepackage style 480 | option [UWforbf] to replace boldface with a wavey underline. 481 | 482 | UWforbf does handle \bf in math mode, but it doesn't work in section titles, 483 | unfortunately, because the titles are not delimited by explicit braces when 484 | printed by the \section command. For the present version, the \bfseries 485 | command still produces bold face, but \bf makes an under-wave. To get under- 486 | waved section titles you can do 487 | 488 | \renewcommand\@seccntformat[1]{\uwave{\csname the#1\endcsname}\hskip 1em} 489 | 490 | and later specify \section[...]{\uwave{...}}. 491 | 492 | In plain TeX there is no \textbf so you should use \useunder{\UWave}{\bf}{}. 493 | You can even skip a step and define the underline or overprint with \useunder: 494 | \useunder{\bgroup\markoverwith{!}\ULon}{\sf}{} 495 | 496 | Some commands, such as \\ and \hskip are given special treatment, but 497 | others (like \marginpar) are not. Support for others can be added by 498 | assigning special meanings in the token register \UL@hook. (In LaTeX do 499 | \addto@hook\UL@hook{\let\command\ULversion}.) The UL versions of commands 500 | should be modelled on \UL@hskip or \UL@cr, and should include the test 501 | "\ifx\ \LA@space". For example, support for "\marginpar" is added through 502 | the hook mechanism. 503 | 504 | All the underlining commands are robust in LaTeX. 505 | 506 | %====================== END INSTRUCTIONS =========================== 507 | 508 | % Previous bug-finders: Esther Hu (\hfill in plain); Lones Smith (\tt\-); 509 | % Steve Anderson (\ooalign accents); Thanassi Protopapas ( { in tables). 510 | % The bug finders' fee is now $0.00; it will double for each new bug found. 511 | % Version (identified by year) 512 | % 1994: 513 | % Many changes! Notably: LaTeX2e options and \emph. Nesting works (somewhat). 514 | % Behavior with inner braces is more consistent (not stripped). \useunder. 515 | % Better underwave (using lasy6). Special underlines are not commented out. 516 | % patch 1995: fix \UL@swender to work in {tabular}; make hyphenation join 517 | % well; crude math support; eliminate \@clb 518 | % 1996: use "\csname ProvidesPackage\endcsname", tidying. 519 | % 1997: fix \\ when LaTeX changed; remove extra overlap in putbox. 520 | % 2000: hook (and marginpar) 521 | % 2004: Fix spacing in \uwave and \xout. \ULleaders hook. 522 | % 2009: Accept \par in argument (\long) 523 | % 2010: Include \dotuline and \dashuline, typeset documentation, add \UL@setULdepth 524 | % 2011: Change \dimen@ to \UL@height 525 | % 2012: Removed \let\par garbage 526 | % 527 | % Send problem reports to asnd@triumf.ca 528 | % 529 | % test integrity: 530 | % brackets: round, square, curly, angle: () [] {} <> 531 | % backslash, slash, vertical, at, dollar, and: \ / | @ $ & 532 | % hat, grave, acute (apostrophe), quote, tilde, under: ^ ` ' " ~ _ 533 | -------------------------------------------------------------------------------- /articles/style-web.scss: -------------------------------------------------------------------------------- 1 | /* stylesheet for Re:VIEW web */ 2 | 3 | $cursor-top: 100px; 4 | $cursor-width: 40px; 5 | $side-width: 200px; 6 | $page-width: 800px; 7 | 8 | nav.side-content 9 | { 10 | 11 | width: $side-width; 12 | position: fixed; 13 | 14 | h1.side-title 15 | { 16 | margin: 0; 17 | padding: 0; 18 | font-size: 1em; 19 | border-top: none; 20 | } 21 | 22 | ul 23 | { 24 | list-style: none; 25 | } 26 | } 27 | 28 | .book-body 29 | { 30 | margin-left: $cursor-width + $side-width; 31 | margin-right: $cursor-width; 32 | position: relative; 33 | 34 | .book-page 35 | { 36 | max-width: $page-width; 37 | margin:0 auto; 38 | padding: 32px 0 32px; 39 | } 40 | 41 | .book-navi 42 | { 43 | position: fixed; 44 | top: 0; 45 | min-width: $cursor-width; 46 | 47 | a 48 | { 49 | text-decoration: none; 50 | } 51 | } 52 | 53 | .book-prev 54 | { 55 | left: $side-width + 10px; 56 | } 57 | .book-next 58 | { 59 | right: 10px; 60 | } 61 | 62 | .book-cursor 63 | { 64 | height: 100vh; 65 | text-align: center; 66 | font-size: 32pt; 67 | padding: $cursor-top 0 0 0; 68 | color: #eee; 69 | } 70 | .book-cursor:hover 71 | { 72 | color: #333; 73 | } 74 | 75 | } 76 | 77 | footer p 78 | { 79 | margin-left: $side-width + $cursor-width; 80 | text-align: center; 81 | } 82 | 83 | .cover-image img 84 | { 85 | max-width: 100%; 86 | } 87 | 88 | -------------------------------------------------------------------------------- /articles/style.scss: -------------------------------------------------------------------------------- 1 | // Variables 2 | $main-color: #64C1D7; 3 | $footer-link-color: #9e9e9e; 4 | $font-size: 16px; 5 | $paragraph-margin-bottom: $font-size * 0.75; 6 | 7 | // デフォルトの装飾を上書く 8 | body { 9 | font-family: "Helvetica Neue", Helvetica, Arial, Meiryo, "メイリオ", sans-serif; 10 | font-size: $font-size; 11 | line-height: 24px / $font-size; 12 | } 13 | h2 { 14 | border-bottom: 1px solid #ccc; 15 | } 16 | h1, h2, h3 { 17 | margin-top: $font-size * 0.875 * 2; 18 | margin-bottom: $font-size * 0.875; 19 | } 20 | h4, h5, h6 { 21 | margin-top: $font-size * 0.875; 22 | margin-bottom: $font-size * 0.875; 23 | &:before { 24 | content: "■"; 25 | } 26 | } 27 | p { 28 | margin-bottom: $paragraph-margin-bottom; 29 | } 30 | dd { 31 | margin-left: 3em; 32 | } 33 | 34 | // コラム 35 | div.column { 36 | border: solid 3px; 37 | border-radius: 10px; 38 | padding-left: 1em; 39 | padding-right: 1em; 40 | margin-top: $paragraph-margin-bottom; 41 | margin-bottom: $paragraph-margin-bottom; 42 | h1, h2, h3 { 43 | margin-top: $font-size * 0.875; 44 | } 45 | } 46 | 47 | // リード文 48 | div.lead { 49 | font-size: $font-size; 50 | margin-left: 3em; 51 | } 52 | 53 | // footnote 54 | .noteref { 55 | vertical-align: super; 56 | font-size: smaller; 57 | } 58 | p.footnote { 59 | font-size: 0.8em; 60 | } 61 | // footnoteは最後に持ってくる 62 | .main-content { 63 | display: flex; 64 | flex-direction: column; 65 | } 66 | div.footnote { 67 | order: 1; 68 | } 69 | 70 | // 画像やリストなどのキャプション系 71 | .image, .table, .caption-code, .cmd-code { 72 | margin-bottom: $paragraph-margin-bottom; 73 | } 74 | .image, .table { // キャプションを中央寄せするもの 75 | .caption { 76 | text-align: center; 77 | } 78 | } 79 | .image { // キャプションに▲を付けるもの 80 | .caption { 81 | margin-top: 0; 82 | &:before { 83 | content: "▲"; 84 | color: lightgray; 85 | } 86 | } 87 | } 88 | .caption-code, .table { // キャプションに▼を付けるもの 89 | .caption { 90 | margin-bottom: 0; 91 | &:before { 92 | content: "▼"; 93 | color: lightgray; 94 | } 95 | } 96 | } 97 | .image { 98 | img { // 画像は中央余生にしとく 99 | display: block; 100 | max-width: 100%; 101 | height: auto; 102 | margin-left: auto;; 103 | margin-right: auto; 104 | } 105 | } 106 | 107 | // ヘッダ 108 | .tb-header { 109 | background-image: url(images/html_header.jpg); 110 | background-size: cover; 111 | height: 20vw; 112 | background-color: $main-color; 113 | color: white; 114 | 115 | // テキストは隠す 116 | text-indent: 150%; 117 | overflow: hidden; 118 | white-space: nowrap; 119 | } 120 | 121 | // メニューまわり 122 | .tb-nav-link { 123 | margin-top: 1em; 124 | margin-bottom: 1em; 125 | a.prev:before { 126 | content: "<<"; 127 | } 128 | a.next:after { 129 | content: ">>"; 130 | } 131 | } 132 | 133 | // フッタ 134 | .tb-footer { 135 | margin-top: 16px; 136 | padding-top: 16px; 137 | padding-bottom: 16px; 138 | color: white; 139 | background-color: #424242; 140 | a { 141 | color: $footer-link-color; 142 | } 143 | p { 144 | margin-right: 10px; 145 | } 146 | } 147 | .tb-footer-links { 148 | li:nth-child(n+2)::before { 149 | color: $footer-link-color; 150 | content: "/"; 151 | margin-right: 16px; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /articles/system-operation.re: -------------------------------------------------------------------------------- 1 | = 運用の話 2 | 3 | TODO 4 | OpenCensusとStackDriver TracingとかDataDogとか使う話書きたかったけどまださほど知見溜まってへん…。 5 | 6 | == メトリクス取りたい 7 | 8 | TODO 9 | 10 | == 実行ログを取得する 11 | 12 | TODO 13 | 14 | == クエリのstats分析したい 15 | 16 | TODO 17 | -------------------------------------------------------------------------------- /build-in-docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # コマンド手打ちで作業したい時は以下の通り /book に pwd がマウントされます 4 | # docker run -i -t -v $(pwd):/book vvakame/review /bin/bash 5 | 6 | docker run -t --rm -v $(pwd):/book vvakame/review:2.5 /bin/bash -ci "cd /book && ./setup.sh && npm run pdf" 7 | -------------------------------------------------------------------------------- /code/best-practice/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | -------------------------------------------------------------------------------- /code/best-practice/complexity-sample/complexity.go: -------------------------------------------------------------------------------- 1 | package complexity_sample 2 | 3 | const ComplexityLimit = 200 4 | 5 | func NewComplexity() ComplexityRoot { 6 | 7 | f := func(childComplexity int, first *int) int { 8 | if first == nil { 9 | // 指定無しはエラーにしたいけどそれはResolver側に任せる 10 | return 1 + childComplexity 11 | } 12 | 13 | return *first * childComplexity 14 | } 15 | 16 | complexityRoot := ComplexityRoot{} 17 | complexityRoot.Query.EventList = f 18 | complexityRoot.Event.CircleList = f 19 | 20 | return complexityRoot 21 | } 22 | -------------------------------------------------------------------------------- /code/best-practice/complexity-sample/gqlgen.yml: -------------------------------------------------------------------------------- 1 | # .gqlgen.yml example 2 | # 3 | # Refer to https://gqlgen.com/config/ 4 | # for detailed .gqlgen.yml documentation. 5 | 6 | schema: schema.graphql 7 | exec: 8 | filename: generated.go 9 | model: 10 | filename: models_gen.go 11 | resolver: 12 | filename: resolver.go 13 | type: Resolver 14 | -------------------------------------------------------------------------------- /code/best-practice/complexity-sample/models_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by github.com/99designs/gqlgen, DO NOT EDIT. 2 | 3 | package complexity_sample 4 | 5 | type Circle struct { 6 | ID string `json:"id"` 7 | Name string `json:"name"` 8 | Event Event `json:"event"` 9 | } 10 | 11 | type Event struct { 12 | ID string `json:"id"` 13 | Name string `json:"name"` 14 | CircleList []Circle `json:"circleList"` 15 | } 16 | -------------------------------------------------------------------------------- /code/best-practice/complexity-sample/resolver.go: -------------------------------------------------------------------------------- 1 | //go:generate gorunpkg github.com/99designs/gqlgen 2 | 3 | package complexity_sample 4 | 5 | import ( 6 | "context" 7 | ) 8 | 9 | func NewResolver() ResolverRoot { 10 | return &Resolver{} 11 | } 12 | 13 | type Resolver struct{} 14 | 15 | func (r *Resolver) Query() QueryResolver { 16 | return &queryResolver{r} 17 | } 18 | 19 | type queryResolver struct{ *Resolver } 20 | 21 | func (r *queryResolver) EventList(ctx context.Context, first *int) ([]Event, error) { 22 | panic("not implemented") 23 | } 24 | -------------------------------------------------------------------------------- /code/best-practice/complexity-sample/schema.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | eventList(first: Int): [Event!]! 3 | } 4 | 5 | type Event { 6 | id: ID! 7 | name: String! 8 | 9 | circleList(first: Int): [Circle!]! 10 | } 11 | 12 | type Circle { 13 | id: ID! 14 | name: String! 15 | 16 | event: Event! 17 | } 18 | -------------------------------------------------------------------------------- /code/best-practice/complexity-sample/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | 8 | "github.com/99designs/gqlgen/handler" 9 | "github.com/vvakame/graphql-with-go-book/code/best-practice/complexity-sample" 10 | ) 11 | 12 | const defaultPort = "8080" 13 | 14 | func main() { 15 | port := os.Getenv("PORT") 16 | if port == "" { 17 | port = defaultPort 18 | } 19 | 20 | http.Handle("/", handler.Playground("GraphQL playground", "/query")) 21 | http.Handle("/query", handler.GraphQL( 22 | complexity_sample.NewExecutableSchema( 23 | complexity_sample.Config{ 24 | Resolvers: complexity_sample.NewResolver(), 25 | Complexity: complexity_sample.NewComplexity(), 26 | }, 27 | ), 28 | handler.ComplexityLimit(complexity_sample.ComplexityLimit), 29 | ), 30 | ) 31 | 32 | log.Printf("connect to http://localhost:%s/ for GraphQL playground", port) 33 | log.Fatal(http.ListenAndServe(":"+port, nil)) 34 | } 35 | -------------------------------------------------------------------------------- /code/best-practice/custom-scalars/custom_scalars.go: -------------------------------------------------------------------------------- 1 | package custom_scalars 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strconv" 7 | 8 | "github.com/99designs/gqlgen/graphql" 9 | ) 10 | 11 | // MarshalGraphQLInt64Scalar returns int64 12 | // to GraphQL Scalar value marshaller. 13 | func MarshalGraphQLInt64Scalar(v int64) graphql.Marshaler { 14 | return graphql.WriterFunc(func(w io.Writer) { 15 | s := fmt.Sprintf(`"%d"`, v) 16 | w.Write([]byte(s)) 17 | }) 18 | } 19 | 20 | // UnmarshalGraphQLInt64Scalar returns int64 value 21 | // from GraphQL value. 22 | func UnmarshalGraphQLInt64Scalar(v interface{}) (int64, error) { 23 | switch v := v.(type) { 24 | case string: 25 | return strconv.ParseInt(v, 10, 64) 26 | case int64: 27 | return v, nil 28 | default: 29 | return 0, fmt.Errorf("%T is not a int64", v) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /code/best-practice/custom-scalars/go.sum: -------------------------------------------------------------------------------- 1 | github.com/99designs/gqlgen v0.5.1 h1:cRsbpZgX83PrXb0/hj5EkNdmaVN4l/Eii81Z8LCexgY= 2 | github.com/99designs/gqlgen v0.5.1/go.mod h1:KSQDfLlTTGmzlRgLGm6HeKKKo598l5E2svEM6Nz2Jnw= 3 | github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ= 4 | github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= 5 | github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= 6 | github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= 7 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 8 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= 10 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 11 | github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= 12 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 13 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 14 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 15 | github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= 16 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 17 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 18 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 19 | github.com/vektah/gqlparser v0.0.0-20180831041411-14e83ae06ec1 h1:FYOXUtr3sYR9shto7Q/aQ1B0Onyk77aws9wGOORiz+I= 20 | github.com/vektah/gqlparser v0.0.0-20180831041411-14e83ae06ec1/go.mod h1:K4QdSSpS2XiHHwzb18kWh3iBljB8rLC8okGXsnQy3Nc= 21 | github.com/vvakame/graphql-with-go-book v0.0.0-20180916085051-160ffcefef3f h1:7tZMsCQE5RfcOnspeorhys+cmWzW5tTeHfhIhJo5hBQ= 22 | github.com/vvakame/graphql-with-go-book/code/best-practice v0.0.0-20180916085051-160ffcefef3f h1:AFKU022NkUxIjXNdfmT6FUO8ygPSPQaFkQOYSKvZa4g= 23 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 24 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 25 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 26 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 27 | -------------------------------------------------------------------------------- /code/best-practice/custom-scalars/gqlgen.yml: -------------------------------------------------------------------------------- 1 | schema: schema.graphql 2 | models: 3 | Int64: 4 | model: github.com/vvakame/graphql-with-go-book/code/best-practice/custom-scalars.GraphQLInt64Scalar 5 | -------------------------------------------------------------------------------- /code/best-practice/custom-scalars/models_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by github.com/99designs/gqlgen, DO NOT EDIT. 2 | 3 | package custom_scalars 4 | 5 | type Sample struct { 6 | ID string `json:"id"` 7 | Value *int64 `json:"value"` 8 | } 9 | -------------------------------------------------------------------------------- /code/best-practice/custom-scalars/resolver.go: -------------------------------------------------------------------------------- 1 | //go:generate gorunpkg github.com/99designs/gqlgen 2 | 3 | package custom_scalars 4 | 5 | import ( 6 | "context" 7 | ) 8 | 9 | func NewResolver() ResolverRoot { 10 | return &Resolver{} 11 | } 12 | 13 | type Resolver struct{} 14 | 15 | func (r *Resolver) Query() QueryResolver { 16 | return &queryResolver{r} 17 | } 18 | 19 | type queryResolver struct{ *Resolver } 20 | 21 | func (r *queryResolver) Sample(ctx context.Context) (*Sample, error) { 22 | panic("not implemented") 23 | } 24 | -------------------------------------------------------------------------------- /code/best-practice/custom-scalars/schema.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | sample: Sample 3 | } 4 | 5 | scalar Int64 6 | 7 | type Sample { 8 | id: ID! 9 | value: Int64 10 | } 11 | -------------------------------------------------------------------------------- /code/best-practice/custom-scalars/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | 8 | "github.com/99designs/gqlgen/handler" 9 | "github.com/vvakame/graphql-with-go-book/code/best-practice/custom_scalars" 10 | ) 11 | 12 | const defaultPort = "8080" 13 | 14 | func main() { 15 | port := os.Getenv("PORT") 16 | if port == "" { 17 | port = defaultPort 18 | } 19 | 20 | http.Handle("/", handler.Playground("GraphQL playground", "/query")) 21 | http.Handle("/query", handler.GraphQL( 22 | custom_scalars.NewExecutableSchema( 23 | custom_scalars.Config{ 24 | Resolvers: custom_scalars.NewResolver(), 25 | }, 26 | ), 27 | ), 28 | ) 29 | 30 | log.Printf("connect to http://localhost:%s/ for GraphQL playground", port) 31 | log.Fatal(http.ListenAndServe(":"+port, nil)) 32 | } 33 | -------------------------------------------------------------------------------- /code/github-v4/go-client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "net/url" 10 | "os" 11 | ) 12 | 13 | func main() { 14 | githubToken := os.Getenv("GITHUB_TOKEN") 15 | query := ` 16 | query ($login: String!) { 17 | organization(login: $login) { 18 | name 19 | email 20 | } 21 | } 22 | ` 23 | b, err := json.Marshal(struct { 24 | Query string `json:"query"` 25 | Variable map[string]interface{} `json:"variables"` 26 | }{ 27 | Query: query, 28 | Variable: map[string]interface{}{ 29 | "login": "github", 30 | }, 31 | }) 32 | if err != nil { 33 | panic(err) 34 | } 35 | 36 | endpointURL, err := url.Parse("https://api.github.com/graphql") 37 | if err != nil { 38 | panic(err) 39 | } 40 | buf := bytes.NewBuffer(b) 41 | resp, err := http.DefaultClient.Do(&http.Request{ 42 | URL: endpointURL, 43 | Method: "POST", 44 | Header: http.Header{ 45 | "Content-Type": {"application/json"}, 46 | "Authorization": {"bearer " + githubToken}, 47 | }, 48 | Body: ioutil.NopCloser(buf), 49 | }) 50 | if err != nil { 51 | panic(err) 52 | } 53 | b, err = ioutil.ReadAll(resp.Body) 54 | if err != nil { 55 | panic(err) 56 | } 57 | 58 | fmt.Println(string(b)) 59 | } 60 | -------------------------------------------------------------------------------- /code/github-v4/text/query-viewer.graphql: -------------------------------------------------------------------------------- 1 | { 2 | viewer { 3 | login 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /code/github-v4/text/query-viewer.graphql.result: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "viewer": { 4 | "login": "vvakame" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /code/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/vvakame/graphql-with-go-book/code 2 | 3 | require ( 4 | github.com/99designs/gqlgen v0.5.1 5 | github.com/agnivade/levenshtein v1.0.1 // indirect 6 | github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 // indirect 7 | github.com/davecgh/go-spew v1.1.1 // indirect 8 | github.com/gorilla/websocket v1.4.0 // indirect 9 | github.com/hashicorp/golang-lru v0.5.0 // indirect 10 | github.com/pmezard/go-difflib v1.0.0 // indirect 11 | github.com/sergi/go-diff v1.0.0 // indirect 12 | github.com/stretchr/testify v1.2.2 // indirect 13 | github.com/vektah/gqlparser v0.0.0-20180919003821-90edd96486c6 14 | gopkg.in/yaml.v2 v2.2.1 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /code/go.sum: -------------------------------------------------------------------------------- 1 | github.com/99designs/gqlgen v0.5.1 h1:cRsbpZgX83PrXb0/hj5EkNdmaVN4l/Eii81Z8LCexgY= 2 | github.com/99designs/gqlgen v0.5.1/go.mod h1:KSQDfLlTTGmzlRgLGm6HeKKKo598l5E2svEM6Nz2Jnw= 3 | github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ= 4 | github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= 5 | github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= 6 | github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= 7 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 8 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= 10 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 11 | github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= 12 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 13 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 14 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 15 | github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= 16 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 17 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 18 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 19 | github.com/vektah/gqlparser v0.0.0-20180919003821-90edd96486c6 h1:0+bK5s5mQIhPElSejgpTh/NiA11fdwQc5PMROLT+zdM= 20 | github.com/vektah/gqlparser v0.0.0-20180919003821-90edd96486c6/go.mod h1:K4QdSSpS2XiHHwzb18kWh3iBljB8rLC8okGXsnQy3Nc= 21 | github.com/vvakame/graphql-with-go-book v0.0.0-20180923135108-2e3fcf05ac0b h1:MF/0jXnmiU61NWjNhizO7N2/uzE75nLp/ch9TxP145Y= 22 | github.com/vvakame/graphql-with-go-book v0.0.0-20180923152357-1161e75bf6e8 h1:orN6qK2sAnB1RLG44MVbd1DiAZUgaefi2FUPwTI+jtk= 23 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 24 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 25 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 26 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-with-go-book", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "", 6 | "main": "Gruntfile.js", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/vvakame/graphql-with-go-book.git" 10 | }, 11 | "author": "vvakame", 12 | "license": "UNLICENSED", 13 | "bugs": { 14 | "url": "https://github.com/vvakame/graphql-with-go-book/issues" 15 | }, 16 | "homepage": "https://github.com/vvakame/graphql-with-go-book#readme", 17 | "engines": { 18 | "node": ">=10.0.0" 19 | }, 20 | "scripts": { 21 | "global-bundler": "gem install bundler", 22 | "global": "npm run global-bundler", 23 | "postinstall": "bundle install", 24 | "check:prh": "prh --verify --verbose articles/*.re", 25 | "check": "npm run check:prh", 26 | "fmt": "npm run fmt:graphql", 27 | "fmt:graphql": "prettier --write 'code/**/*.graphql'", 28 | "pdf": "grunt pdf", 29 | "md": "grunt markdown", 30 | "html": "grunt html", 31 | "test": "npm run check && npm run html" 32 | }, 33 | "dependencies": {}, 34 | "devDependencies": { 35 | "@types/node": "^10.10.3", 36 | "grunt": "^1.0.3", 37 | "grunt-cli": "^1.3.1", 38 | "grunt-contrib-clean": "^2.0.0", 39 | "grunt-open": "0.2.3", 40 | "grunt-shell": "^2.1.0", 41 | "js-yaml": "^3.6.0", 42 | "load-grunt-tasks": "^4.0.0", 43 | "prettier": "^1.14.3", 44 | "prh": "^5.4.3", 45 | "prh-languageserver": "^1.0.1", 46 | "typescript": "^3.0.3" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /redpen-conf-ja.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 37 | 38 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eux 4 | 5 | rm -rf node_modules 6 | # --unsafe-perm はrootでの実行時(= docker環境)で必要 非root時の挙動に影響なし 7 | npm install --unsafe-perm 8 | git submodule init && git submodule update 9 | --------------------------------------------------------------------------------