wcコマンド改良&&メモアプリ試作
This commit is contained in:
parent
bff969520f
commit
473407048c
12 changed files with 337 additions and 10 deletions
4
memoapp/Gemfile
Normal file
4
memoapp/Gemfile
Normal file
|
@ -0,0 +1,4 @@
|
|||
source "https://rubygems.org"
|
||||
|
||||
gem "sinatra"
|
||||
gem "sinatra-contrib"
|
38
memoapp/Gemfile.lock
Normal file
38
memoapp/Gemfile.lock
Normal file
|
@ -0,0 +1,38 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
base64 (0.2.0)
|
||||
multi_json (1.15.0)
|
||||
mustermann (3.0.0)
|
||||
ruby2_keywords (~> 0.0.1)
|
||||
rack (3.1.7)
|
||||
rack-protection (4.0.0)
|
||||
base64 (>= 0.1.0)
|
||||
rack (>= 3.0.0, < 4)
|
||||
rack-session (2.0.0)
|
||||
rack (>= 3.0.0)
|
||||
ruby2_keywords (0.0.5)
|
||||
sinatra (4.0.0)
|
||||
mustermann (~> 3.0)
|
||||
rack (>= 3.0.0, < 4)
|
||||
rack-protection (= 4.0.0)
|
||||
rack-session (>= 2.0.0, < 3)
|
||||
tilt (~> 2.0)
|
||||
sinatra-contrib (4.0.0)
|
||||
multi_json (>= 0.0.2)
|
||||
mustermann (~> 3.0)
|
||||
rack-protection (= 4.0.0)
|
||||
sinatra (= 4.0.0)
|
||||
tilt (~> 2.0)
|
||||
tilt (2.4.0)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
sinatra
|
||||
sinatra-contrib
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.11
|
2
memoapp/README.md
Normal file
2
memoapp/README.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
# memoapp
|
||||
Sinatraでシンプルなメモアプリを作る
|
62
memoapp/app.rb
Normal file
62
memoapp/app.rb
Normal file
|
@ -0,0 +1,62 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'sinatra'
|
||||
require 'sinatra/reloader' if development?
|
||||
require 'json'
|
||||
|
||||
FILE_PATH = 'public/memos.json'
|
||||
|
||||
def load_memos
|
||||
File.exist?(FILE_PATH) ? JSON.parse(File.read(FILE_PATH)) : {}
|
||||
end
|
||||
|
||||
def save_memos(memos)
|
||||
File.open(FILE_PATH, 'w') do |file|
|
||||
file.write(JSON.pretty_generate(memos))
|
||||
end
|
||||
end
|
||||
|
||||
get '/' do
|
||||
redirect '/memos'
|
||||
end
|
||||
|
||||
get '/memos' do
|
||||
@memos = load_memos
|
||||
erb :index
|
||||
end
|
||||
|
||||
get '/memos/new' do
|
||||
erb :new
|
||||
end
|
||||
|
||||
post '/memos' do
|
||||
memos = load_memos
|
||||
id = (memos.keys.map(&:to_i).max || 0) + 1
|
||||
memos[id.to_s] = { 'title' => params[:title], 'content' => params[:content] }
|
||||
save_memos(memos)
|
||||
redirect '/memos'
|
||||
end
|
||||
|
||||
get '/memos/:id' do
|
||||
@memo = load_memos[params[:id]]
|
||||
erb :show
|
||||
end
|
||||
|
||||
get '/memos/:id/edit' do
|
||||
@memo = load_memos[params[:id]]
|
||||
erb :edit
|
||||
end
|
||||
|
||||
patch '/memos/:id' do
|
||||
memos = load_memos
|
||||
memos[params[:id]] = { 'title' => params[:title], 'content' => params[:content] }
|
||||
save_memos(memos)
|
||||
redirect "/memos/#{params[:id]}"
|
||||
end
|
||||
|
||||
delete '/memos/:id' do
|
||||
memos = load_memos
|
||||
memos.delete(params[:id])
|
||||
save_memos(memos)
|
||||
redirect '/memos'
|
||||
end
|
10
memoapp/public/memos.json
Normal file
10
memoapp/public/memos.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"1": {
|
||||
"title": "あ",
|
||||
"content": "い"
|
||||
},
|
||||
"2": {
|
||||
"title": "い",
|
||||
"content": "え"
|
||||
}
|
||||
}
|
149
memoapp/public/styles.css
Normal file
149
memoapp/public/styles.css
Normal file
|
@ -0,0 +1,149 @@
|
|||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f0f0f0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 8px;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 24px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
nav {
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
nav a {
|
||||
margin: 0 10px;
|
||||
text-decoration: none;
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
nav a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
form label {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
form input[type="text"],
|
||||
form textarea {
|
||||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
form button {
|
||||
padding: 10px;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
form button.save {
|
||||
background-color: #007bff;
|
||||
}
|
||||
|
||||
form button.save:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
form button.delete {
|
||||
background-color: #dc3545;
|
||||
}
|
||||
|
||||
form button.delete:hover {
|
||||
background-color: #c82333;
|
||||
}
|
||||
|
||||
form button.edit {
|
||||
background-color: #28a745;
|
||||
}
|
||||
|
||||
form button.edit:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
form button.back {
|
||||
background-color: #6c757d;
|
||||
}
|
||||
|
||||
form button.back:hover {
|
||||
background-color: #5a6268;
|
||||
}
|
||||
|
||||
.memo-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.memo-list li {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.memo-list a {
|
||||
text-decoration: none;
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.memo-list a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.memo-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px; /* 追加: 「戻る」ボタンの上に余白を追加 */
|
||||
}
|
||||
|
||||
.memo-actions form {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.memo-actions button {
|
||||
padding: 10px;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.memo-actions .edit {
|
||||
background-color: #28a745; /* 緑色に統一 */
|
||||
}
|
||||
|
||||
.memo-actions .edit:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
.memo-actions .back {
|
||||
background-color: #6c757d;
|
||||
}
|
||||
|
8
memoapp/views/edit.erb
Normal file
8
memoapp/views/edit.erb
Normal file
|
@ -0,0 +1,8 @@
|
|||
<form action="/memos/<%= params[:id] %>" method="post">
|
||||
<input type="hidden" name="_method" value="patch">
|
||||
<label for="title">タイトル</label>
|
||||
<input type="text" name="title" id="title" value="<%= @memo["title"] %>">
|
||||
<label for="content">内容</label>
|
||||
<textarea name="content" id="content"><%= @memo["content"] %></textarea>
|
||||
<button type="submit" class="edit">変更</button>
|
||||
</form>
|
5
memoapp/views/index.erb
Normal file
5
memoapp/views/index.erb
Normal file
|
@ -0,0 +1,5 @@
|
|||
<ul class="memo-list">
|
||||
<% @memos.each do |id, memo| %>
|
||||
<li><a href="/memos/<%= id %>"><%= memo["title"] %></a></li>
|
||||
<% end %>
|
||||
</ul>
|
22
memoapp/views/layout.erb
Normal file
22
memoapp/views/layout.erb
Normal file
|
@ -0,0 +1,22 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="ja">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>メモアプリ</title>
|
||||
<link rel="stylesheet" href="/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>メモアプリ</h1>
|
||||
</header>
|
||||
<nav>
|
||||
<a href="/memos">メモ一覧</a>
|
||||
<a href="/memos/new">新規メモ作成</a>
|
||||
</nav>
|
||||
<main>
|
||||
<%= yield %>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
7
memoapp/views/new.erb
Normal file
7
memoapp/views/new.erb
Normal file
|
@ -0,0 +1,7 @@
|
|||
<form action="/memos" method="post">
|
||||
<label for="title">タイトル</label>
|
||||
<input type="text" name="title" id="title">
|
||||
<label for="content">内容</label>
|
||||
<textarea name="content" id="content"></textarea>
|
||||
<button type="submit" class="save">保存</button>
|
||||
</form>
|
14
memoapp/views/show.erb
Normal file
14
memoapp/views/show.erb
Normal file
|
@ -0,0 +1,14 @@
|
|||
<h2><%= @memo["title"] %></h2>
|
||||
<p><%= @memo["content"] %></p>
|
||||
<div class="memo-actions">
|
||||
<form action="/memos/<%= params[:id] %>/edit" method="get">
|
||||
<button type="submit" class="edit">変更</button>
|
||||
</form>
|
||||
<form action="/memos/<%= params[:id] %>" method="post">
|
||||
<input type="hidden" name="_method" value="delete">
|
||||
<button type="submit" class="delete">削除</button>
|
||||
</form>
|
||||
</div>
|
||||
<form action="/memos" method="get">
|
||||
<button type="submit" class="back">戻る</button>
|
||||
</form>
|
26
wc.rb
26
wc.rb
|
@ -8,7 +8,7 @@ def main
|
|||
total_stats = calculate_total_stats(file_stats)
|
||||
max_widths = calculate_max_widths(file_stats, total_stats)
|
||||
print_file_stats(file_stats, max_widths, options)
|
||||
print_file_stats([['合計', total_stats]], max_widths, options) if sources.size > 1
|
||||
print_file_stats([{ filename: '合計', **total_stats }], max_widths, options) if sources.size > 1
|
||||
end
|
||||
|
||||
def parse_options
|
||||
|
@ -26,31 +26,37 @@ end
|
|||
def collect_file_stats(sources)
|
||||
sources.map do |source|
|
||||
input = source.empty? ? ARGF.read : File.read(source)
|
||||
stats = { lines: input.lines.count, words: input.split.size, bytes: input.bytesize }
|
||||
[source, stats]
|
||||
{ filename: source, lines: input.lines.count, words: input.split.size, bytes: input.bytesize }
|
||||
end
|
||||
end
|
||||
|
||||
def calculate_total_stats(file_stats)
|
||||
file_stats.reduce({ lines: 0, words: 0, bytes: 0 }) do |total, (_, stats)|
|
||||
total.merge(stats) { |_, a, b| a + b }
|
||||
total_stats = { lines: 0, words: 0, bytes: 0 }
|
||||
file_stats.each do |stats|
|
||||
total_stats[:lines] += stats[:lines]
|
||||
total_stats[:words] += stats[:words]
|
||||
total_stats[:bytes] += stats[:bytes]
|
||||
end
|
||||
total_stats
|
||||
end
|
||||
|
||||
def calculate_max_widths(file_stats, total_stats)
|
||||
(file_stats.map(&:last) + [total_stats]).each_with_object({ lines: 0, words: 0, bytes: 0 }) do |stats, max_widths|
|
||||
max_widths.merge!(stats) { |_, max, stat| [max, stat.to_s.length].max }
|
||||
all_stats = file_stats + [total_stats]
|
||||
%i[lines words bytes].each_with_object({}) do |key, max_widths|
|
||||
max_widths[key] = all_stats.map { |stats| stats[key].to_s.length }.max
|
||||
end
|
||||
end
|
||||
|
||||
def format_result(stats, max_widths, options)
|
||||
%i[lines words bytes].map { |key| stats[key].to_s.rjust(max_widths[key]) if options[key] }.compact.join(' ')
|
||||
%i[lines words bytes].filter_map do |key|
|
||||
stats[key].to_s.rjust(max_widths[key]) if options[key]
|
||||
end.join(' ')
|
||||
end
|
||||
|
||||
def print_file_stats(file_stats, max_widths, options)
|
||||
file_stats.each do |source, stats|
|
||||
file_stats.each do |stats|
|
||||
result = format_result(stats, max_widths, options)
|
||||
puts "#{result} #{source}"
|
||||
puts "#{result} #{stats[:filename]}"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue