結論 safe_joinを使えば、タグをエスケープしてXSS攻撃を防ぎつつ、連続した改行も反映される。 <% text = "1行目\n\n\n2行目<script>alert()</script>" %> <%= safe_join(text.split("\n"), tag.br) %> # 1行目 # # # 2行目<script>alert()</script> 結論に至るまでの道のり その1 <% text = "1行目\n\n\n2行目<script>alert()</script>" %> <%= text.gsub("\n", '<br>').html_safe %> # 1行目 # 2行目 ❌タグのエスケープ ❌連続した改行の反映 連続した改行は反映されないし、alert()が実行されてしまう。 その2 <% text = "1行目\n\n\n2行目<script>alert()</script>" %> <%= html_escape(text).gsub("\n", '<br>').html_safe %> # 1行目 # # # 2行目<script>alert()</script> ✅タグのエスケープ ✅連続した改行の反映 しかし、これだとrubocopのRails/OutputSafetyで怒られる。 またRails API:String#html_safeにもあるように、html_safe()をユーザー入力に対して使うのは非推奨とされている。 その3 <% text = "1行目\n\n\n2行目<script>alert()</script>" %> <%= simple_format(html_escape(text)) %> # 1行目 # # 2行目<script>alert()</script> ✅タグのエスケープ ❌連続した改行の反映 改行が1つにまとめられてしまう。 その4 <% text = "1行目\n\n\n2行目<script>alert()</script>" %> <%= safe_join(text.split("\n"), tag.br) %> # 1行目 # # # 2行目<script>alert()</script> ✅タグのエスケープ ✅連続した改行の反映 結論、タグのエスケープと連続した改行の反映を行いたい場合は、safe_joinを使おう。 ...
【Vue3/create-vue】SPA版メモアプリを作る
この記事ではVue.jsで基本的なSPA(Single Page Application)を作成する方法を紹介します。 create-vueを使用 Vue3でComposition APIを使用 データはLocalStorageに保存 PiniaやVue Routerは使用しない 1. 実行環境 macOS:13.1 Node.js:18.12.1 npm:8.19.2 Vue:3.2.45 create-vue:3.5.0 Vite:4.0.4 2. アプリの要件 以下の機能を持つメモアプリを作成します。 一覧 メモの1行目を一覧表示する。タイトルをクリックするとそのメモの編集状態に移行する。 詳細 編集状態 = 詳細 追加 新規作成ボタンをクリックすると新規メモが作成され、編集状態に移行する。 編集 テキストエリアにメモの内容を表示し、編集できる。編集ボタンをクリックすると保存される。 削除 編集状態で削除ボタンをクリックするとメモは削除される。 3. 作成手順 3-1. create-vueを実行 $ npm create vue@3 Vue.js - The Progressive JavaScript Framework ✔ Project name: … vue-project ✔ Add TypeScript? … No ✔ Add JSX Support? … No ✔ Add Vue Router for Single Page Application development? … No ✔ Add Pinia for state management? … No ✔ Add Vitest for Unit Testing? … No ✔ Add an End-to-End Testing Solution? › No ✔ Add ESLint for code quality? … Yes ✔ Add Prettier for code formatting? … Yes ESLintとPrettier以外全部No $ cd vue-project $ git init $ npm install $ npm run dev git initは任意のタイミングで npm run dev実行後、ブラウザからhttp://localhost:5173/にアクセスして確認。 3-2. 不要なファイルとコードを削除 以下のファイルを削除します。 ...
【Node.js】npmパッケージの公開方法
この記事ではnpmパッケージの公開・バージョンアップ・削除方法を紹介します。 npmパッケージを公開するには、事前にnpmアカウントの作成とGitHubリポジトリの作成が必要です。 docs.npmjs.com/creating-a-new-npm-user-account 1. 環境 macOS:13.1 Node.js:18.12.1 npm:8.19.2 2. npmパッケージの公開 2-1. package.jsonの作成 公開するnpmパッケージにはpackage.jsonが含まれている必要があります。 プロジェクトのルートディレクトリで以下のコマンドを実行してpackage.jsonを作成します。 $ npm init プロンプトに表示された各項目の質問に答えると、package.jsonが作成されます。 Creating a package.json file 次に、作成されたpackage.jsonにbinの項目を追加します。 "bin": { "<package-name>": "./index.js" }, 上記を追加することで、公開したパッケージをnpx <package-name>で使用できるようになります。 package.json - bin 2-2. 依存するパッケージの追加 Specifying dependencies and devDependencies in a package.json file プロジェクトが依存するパッケージをpackage.jsonの"dependencies"または"devDependencies"に追加する必要があります。 "dependencies": アプリケーションの実行に必要なパッケージ package.json - dependencies "devDependencies": ローカルでの開発・テストにのみ必要なパッケージ package.json - devDependencies プロジェクトのルートディレクトリで以下のコマンドを実行して依存するパッケージを追加します。 # "dependencies"に追加する場合 $ npm install <package-name> # "devDependencies"に追加する場合 $ npm install <package-name> --save-dev npm-install | npm Docs 2-3. npmレジストリへの公開 Creating and publishing unscoped public packages 最後に作成したパッケージをnpmレジストリに公開します。 ...
【Node.js】CLI版メモアプリを作る
この記事では、Node.jsでメモの追加・一覧・参照・削除ができるコマンドラインアプリを作成します。 データの保存先にはJSONファイルを使い、JavaScriptのclass構文を使って作成します。 1. 実行環境 macOS:13.0.1 Node.js:18.12.1 npm:8.19.2 enquirer:2.3.6 minimist:1.2.7 2. アプリの要件 以下の機能を持つメモアプリを作成します。 2-1. メモの追加 標準入力に入ってきたテキストを新しいメモとして追加する。 $ echo 'メモの内容' | app.js 2-2. メモの一覧 それぞれのメモの最初の行のみを表示する。 $ app.js -l メモ1の1行目 メモ2の1行目 メモ3の1行目 2-3. メモの参照 選んだメモの全文が表示される。 $ app.js -r Choose a note you want to see: メモ1の1行目 メモ2の1行目 > メモ3の1行目 メモ3の1行目 メモ3の2行目 メモ3の3行目 2-4. メモの削除 選んだメモが削除される。 $ app.js -d Choose a note you want to delete: メモ1の1行目 メモ2の1行目 > メモ3の1行目 Successfully deleted !! 3. ソースコード app.js ...
【RUBY_FUNCTION_NAME_STRING】rbenv installでBUILD FAILEDが出てRuby 3.1.1がインストールできない時の対処法
1. 環境 MacBook Pro (13-inch, 2020) macOS Ventura 13.1 Homebrew:3.6.20(※) xcode-select:2396(※) ※ エラー解決後のバージョン 2. 発生した問題 rbenvでRuby 3.1.1をインストールしようとしたら以下のエラーが発生。 $ rbenv install 3.1.1 Downloading openssl-3.0.5.tar.gz... -> https://dqw8nmjcqpjn7.cloudfront.net/aa7d8d9bef71ad6525c55ba11e5f4397889ce49c2c9349dcea6d3e4f0b024a7a Installing openssl-3.0.5... Installed openssl-3.0.5 to /Users/kazunoko/.rbenv/versions/3.1.1 Downloading ruby-3.1.1.tar.gz... -> https://cache.ruby-lang.org/pub/ruby/3.1/ruby-3.1.1.tar.gz Installing ruby-3.1.1... ruby-build: using readline from homebrew BUILD FAILED (macOS 13.1 using ruby-build 20220726) Inspect or clean up the working tree at /var/folders/m9/r1bz5qns0nj2ysmyrqwpn3j00000gn/T/ruby-build.20230123081732.42760.Fk3kTB Results logged to /var/folders/m9/r1bz5qns0nj2ysmyrqwpn3j00000gn/T/ruby-build.20230123081732.42760.log Last 10 log lines: ^ In file included from compile.c:40: ./vm_callinfo.h:216:16: error: use of undeclared identifier 'RUBY_FUNCTION_NAME_STRING' if (debug) rp(ci); ^ ./internal.h:94:72: note: expanded from macro 'rp' #define rp(obj) rb_obj_info_dump_loc((VALUE)(obj), __FILE__, __LINE__, RUBY_FUNCTION_NAME_STRING) ^ 2 errors generated. make: *** [compile.o] Error 1 出力された/var/folders/m9/r1bz5qns0nj2ysmyrqwpn3j00000gn/T/ruby-build.20230123081732.42760.logを確認(最後の方だけ抜粋)。 ...
【Node.js】フラッシュ暗算ゲームを作成する
CLIでフラッシュ暗算ができるnpmパッケージを作成しました。 djkazunoko/flash-anzan npmパッケージの公開方法は以下を参照ください。 【Node.js】npmパッケージの公開方法 - あまブログ この記事では、JavaScriptでフラッシュ暗算ゲームを作成する方法を解説します。 1. 実行環境 macOS:13.1 Node.js:18.12.1 npm:8.19.2 enquirer:2.3.6 2. フラッシュ暗算ゲームの仕様 桁数、表示回数、表示間隔を選択する カウントダウンの後に問題を出題する 回答を入力する 正解を表示する 3. ソースコード index.js #! /usr/bin/env node const { prompt } = require("enquirer"); async function main() { const terms = await flashNumbers(); await checkAnswer(terms); } async function flashNumbers() { const options = await getOptions(); const terms = []; await countDown(); for (let i = 0; i < options.displayCount; i++) { const prevNum = terms.slice(-1)[0]; let num = getNumber(options); while (num === prevNum) { num = getNumber(options); } terms.push(num); await displayNumber(num); await new Promise((resolve) => setTimeout(resolve, options.displayInterval * 1000) ); } process.stdout.clearLine(0); process.stdout.cursorTo(0); return terms; } async function getOptions() { return await prompt([ { type: "input", name: "digits", message: "Number of Digits", validate: isPositiveInteger, initial: 1, }, { type: "input", name: "displayCount", message: "Display Count", validate: isPositiveInteger, initial: 10, }, { type: "input", name: "displayInterval", message: "Display Interval(seconds)", validate: isPositiveNumber, initial: 1, }, ]); } function isPositiveInteger(input) { const num = Number(input); return Number.isInteger(num) && num > 0 ? true : "Please input a Positive Integer"; } function isPositiveNumber(input) { const num = Number(input); return num > 0 ? true : "Please input a Positive Number"; } async function countDown() { const texts = [ "\x1b[31mReady\x1b[0m", "\x1b[33mSet\x1b[0m", "\x1b[32mGo!\x1b[0m", ]; for (const text of texts) { process.stdout.clearLine(0); process.stdout.cursorTo(0); process.stdout.write(text); await new Promise((resolve) => setTimeout(resolve, 1000)); } } function getNumber(options) { return Math.floor( Math.random() * Math.pow(10, options.digits) * 0.9 + Math.pow(10, options.digits - 1) ); } async function displayNumber(num) { process.stdout.clearLine(0); process.stdout.cursorTo(0); process.stdout.write(String(num)); } async function checkAnswer(terms) { const correctAnswer = terms.reduce((sum, term) => sum + term); const answer = await inputAnswer(); const result = Number(answer.answer) === correctAnswer ? "\x1b[32mCorrect!\x1b[0m" : "\x1b[31mWrong...\x1b[0m"; const yourAnswer = Number(answer.answer) === correctAnswer ? `\x1b[32m${answer.answer}\x1b[0m` : `\x1b[31m${answer.answer}\x1b[0m`; console.log(result); console.log(`your answer: ${yourAnswer}`); console.log(`correct answer: \x1b[32m${correctAnswer}\x1b[0m`); console.log(`${terms.join(" + ")} = ${correctAnswer}`); } async function inputAnswer() { const question = { type: "input", name: "answer", message: "Please enter your answer", validate: isPositiveInteger, initial: 10, }; return await prompt(question); } main(); 4. ポイント解説 4-1. enquirer 参考:enquirer ...
【Vue3】ToDoリストアプリを作る
この記事ではVue.jsでToDoリストアプリを作成する方法を紹介します。 HTML, CSS, JSだけを使用し、データはLocalStorageに保存し、Vue3でOptions APIを使用します。 1. 実行環境 macOS:13.0.1 Node.js:18.12.1 npm:8.19.2 Vue:3.2.45 2. ToDoアプリの要件 以下のToDoアプリを作成します。 ToDoが登録できる ToDoが一覧表示できる ToDoが編集できる ToDoが削除できる ToDoの処理状態を変更できる ToDoの処理状態ごとに表示できる 3. 作成手順 3-1. ToDoの登録と一覧表示 index.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>ToDoアプリ</title> <link rel="stylesheet" href="style.css" /> </head> <body> <div id="app"> <form @submit.prevent="addTodo"> <input v-model="newTodo" /> <button>登録</button> </form> <ul> <li v-for="todo in todos" :key="todo.id"> {{ todo.text }} </li> </ul> </div> <script type="module" src="app.js"></script> </body> </html> @submit.prevent HTMLFormElement: submit イベント - Web API | MDN イベント修飾子 | Vue.js app.js ...
VSCodeでNode.jsをデバッグする
VSCodeでNode.jsをデバッグする方法は主に以下の3つがあります。 Auto Attach JavaScriptデバッグターミナル launch.jsonを使ったデバッグ この記事では、Auto AttachとJavaScriptデバッグターミナルでのデバッグ方法を紹介します。 1. Auto Attach 1.コマンドパレット(shift + ⌘ + P)でToggle Auto Attachを選択 2.Alwaysを選択 Always:VSCodeの統合ターミナルで起動されたすべての Node.jsプロセスがデバッグされる 以降はステータスバーのAuto Attachから設定変更可能 3.ブレイクポイントを設定して、VSCodeの統合ターミナルからNode.jsを実行 統合ターミナルを開く : control + ` 2. JavaScriptデバッグターミナル JavaScriptデバッグターミナルで実行したNode.jsプロセスは自動的にデバッグされます。 1.ターミナルを開く(control + `) 2.ターミナルスイッチャーのドロップダウンメニューから「JavaScriptデバッグターミナル」を選択 ターミナルスイッチャーのドロップダウンメニュー:ターミナルパネルの右側のvのようなマーク またはコマンドパレットでJavaScript Debug Terminalを選択 3.ブレイクポイントを設定して、JavaScriptデバッグターミナルからNode.jsを実行 【参考】 Node.js debugging in VS Code
【Ruby3.1】lsコマンドを作る(OOP版)
非OOP版はこちら↓ 【Ruby3.1】lsコマンドを作る | あまブログ 1. 実行環境 macOS:13.0.1 Ruby:3.1.0 2. ソースコード ls.rb #!/usr/bin/env ruby # frozen_string_literal: true require_relative 'command' LS::Command.new(ARGV).list_files command.rb # frozen_string_literal: true require 'optparse' require 'pathname' require_relative 'file_stat' require_relative 'long_formatter' require_relative 'short_formatter' module LS class Command def initialize(argv) @params = argv.getopts('alr') @target_dir = Pathname(argv[0] || '.') end def list_files files = build_files @params['l'] ? LongFormatter.new(files).list_files : ShortFormatter.new(files).list_files end private def build_files pattern = @target_dir.join('*') paths = @params['a'] ? Dir.glob(pattern, File::FNM_DOTMATCH) : Dir.glob(pattern) paths = paths.reverse if @params['r'] paths.map { |path| FileStat.new(path) } end end end file_stat.rb ...
【Node.js】カレンダーのプログラムを作る
以下のカレンダーのプログラムをJavaScriptで、nodejsで実行するコマンドラインのプログラムとして作り直します。 【Ruby 3.1】カレンダーのプログラムを作る | あまブログ 1. 環境 macOS:13.0.1 node:v18.12.1 2. ソースコード #!/usr/bin/env node const argv = require("minimist")(process.argv.slice(2)); const today = new Date(); const month = argv.m || today.getMonth() + 1; const year = argv.y || today.getFullYear(); const startOfMonth = new Date(year, month - 1); const endOfMonth = new Date(year, month, 0); console.log(` ${month}月 ${year}`); console.log("日 月 火 水 木 金 土"); process.stdout.write(" ".repeat(startOfMonth.getDay() * 3)); for (const d = startOfMonth; d <= endOfMonth; d.setDate(d.getDate() + 1)) { let day = String(d.getDate()).padStart(2, " "); const color_reverse = "\x1b[7m"; const color_reset = "\x1b[0m"; if ( d.getFullYear() == today.getFullYear() && d.getMonth() == today.getMonth() && d.getDate() == today.getDate() ) { day = `${color_reverse}${day}${color_reset}`; } process.stdout.write(`${day} `); if (d.getDay() == 6) { process.stdout.write("\n"); } } process.stdout.write("\n\n"); 【参考】 Date - JavaScript | MDN minimist - npm Loop through a date range with JavaScript - Stack Overflow 【Node.js】 コンソール(CLI)出力に色や装飾をつける方法