sora 24.02.25 → 24.02.26

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 595fdec94ad7aa62fa2490265ffd6633b039773570518115afd13446cf75f5aa
4
- data.tar.gz: bab57e34ef9eb436ea590c8e5a3988cdd780e4ae657a254c5ecaadff82ef7239
3
+ metadata.gz: 488c9fe33a89000965087732638fafd292137467592fb99dc5341b45665a8b8a
4
+ data.tar.gz: 28c98019e9d9c51260264aec86c558fbc53538ae0aab46da688cd11a5623ee91
5
5
  SHA512:
6
- metadata.gz: 221d63074168d433bbff41cb5507e381fa683b9e6ba8c5a46234b3c6e0764d38e0097582eda7981eb156098783058ef968a8612509813767bf8535d3fd2ff4af
7
- data.tar.gz: 6c70324213cbf7a197939305b3c8892d3a4a7656ad6b689773fdc836354545f460ca06a257e899e034c374b22bf338c317426c0f2414e0115d2bb419df6c728c
6
+ metadata.gz: 022c2e1247b89595b5e0679739d4d99980d049a782b9ed1f4eaebedd89f92ea765cfc31afa5322dad4697ce45621502d376ed8d126d346f147257a935dd44b25
7
+ data.tar.gz: '0798d5c364a33d00dfd422badd7f544f1175ed09ce158294c11f5e881d1a3eae743e169301d2be4a7973da1920d8dab69a57018928f110184a80917d4e3edcfb'
data/.rubocop.yml CHANGED
@@ -17,3 +17,6 @@ Layout/EndOfLine:
17
17
 
18
18
  Metrics/MethodLength:
19
19
  Enabled: False
20
+
21
+ Metrics/AbcSize:
22
+ Enabled: False
data/README.md CHANGED
@@ -2,3 +2,19 @@
2
2
  sora is an http server in Ruby that can send and receive strings and files.
3
3
 
4
4
  sora (Server Of Ruby for Access) can send and receive files, and transmit text strings to communicate information.
5
+
6
+ - https://rubygems.org/gems/sora
7
+
8
+ ## Installation
9
+ This gem is supported on Windows.
10
+
11
+ ### How to Install
12
+ ```ps1
13
+ gem install sora
14
+ ```
15
+
16
+ or
17
+
18
+ ```ps1
19
+ rake install # Install from repository
20
+ ```
data/cgi-bin/get.rb ADDED
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cgi"
4
+ require "json"
5
+ require "securerandom"
6
+
7
+ def get_content_type(filename)
8
+ content_types = {
9
+ ".txt" => "text/plain",
10
+ ".json" => "application/json",
11
+ ".js" => "text/javascript",
12
+ ".css" => "text/css",
13
+ ".csv" => "text/csv",
14
+ ".pdf" => "application/pdf",
15
+ ".xls" => "application/vnd.ms-excel",
16
+ ".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
17
+ ".doc" => "application/msword",
18
+ ".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
19
+ ".ppt" => "application/vnd.ms-powerpoint",
20
+ ".pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation",
21
+ ".gif" => "image/gif",
22
+ ".bmp" => "image/bmp",
23
+ ".svg" => "image/svg+xml",
24
+ ".zip" => "application/zip",
25
+ ".lzh" => "application/x-lzh",
26
+ ".tar" => "application/x-tar",
27
+ ".mp3" => "audio/mpeg",
28
+ ".mp4" => "video/mp4",
29
+ ".mpeg" => "video/mpeg",
30
+ ".jpg" => "image/jpeg",
31
+ ".jpeg" => "image/jpeg",
32
+ ".png" => "image/png",
33
+ ".html" => "text/html"
34
+ }
35
+
36
+ ext = File.extname(filename)
37
+ content_types[ext] || "text/html"
38
+ end
39
+
40
+ def file
41
+ cgi = CGI.new
42
+ params = cgi.params
43
+ unless params.key?("filename")
44
+ print cgi.header("text/plain")
45
+ return
46
+ end
47
+
48
+ file_path = File.join(File.expand_path("../data"), params["filename"][0])
49
+ File.open(file_path, "rb") do |f|
50
+ cgi.out(
51
+ {
52
+ "type" => get_content_type(file_path),
53
+ "content-disposition" => "inline; filename=#{File.basename(file_path)}"
54
+ }
55
+ ) do
56
+ f.read
57
+ end
58
+ end
59
+ end
60
+
61
+ file
data/cgi-bin/main.rb CHANGED
@@ -7,8 +7,15 @@ require "securerandom"
7
7
  cgi = CGI.new
8
8
  print cgi.header("application/json")
9
9
  params = cgi.params
10
+
10
11
  @json_file_path = "../data/data.json"
11
12
 
13
+ def log(text)
14
+ open("tmp", "a") do |f|
15
+ f.puts(text)
16
+ end
17
+ end
18
+
12
19
  def read_json(filename)
13
20
  unless File.exist? filename
14
21
  File.open(filename, "w") do |f|
@@ -57,5 +64,54 @@ def delete(params)
57
64
  print JSON.pretty_generate(response)
58
65
  end
59
66
 
67
+ def save_file(params)
68
+ return unless params.key?("file")
69
+
70
+ file = params["file"][0]
71
+ response = {
72
+ filename: file.original_filename
73
+ }
74
+
75
+ return if [".gitignore", "data.json"].include?(response[:filename])
76
+
77
+ file_path = File.join(File.expand_path("../data"), response[:filename])
78
+
79
+ File.open(file_path, "wb") do |f|
80
+ f.write file.read
81
+ end
82
+
83
+ print JSON.pretty_generate(response)
84
+ end
85
+
86
+ def remove_file(params)
87
+ return unless params.key?("removefile")
88
+
89
+ removefile = params["removefile"][0]
90
+ response = {
91
+ filename: removefile,
92
+ status: "NG"
93
+ }
94
+
95
+ return if [".gitignore", "data.json"].include?(response[:filename])
96
+
97
+ file_path = File.join(File.expand_path("../data"), response[:filename])
98
+
99
+ response[:status] = "OK" if File.exist?(file_path) && File.delete(file_path)
100
+
101
+ print JSON.pretty_generate(response)
102
+ end
103
+
104
+ def get_files(params)
105
+ return unless params.key?("files")
106
+
107
+ response = Dir.glob(File.join(File.expand_path("../data"), "*"))
108
+ response.map! { |e| File.basename(e) }
109
+ response.filter! { |e| !["data.json", ".gitignore"].include?(e) }
110
+ print JSON.pretty_generate(response)
111
+ end
112
+
60
113
  get_json(params)
61
114
  delete(params)
115
+ save_file(params)
116
+ get_files(params)
117
+ remove_file(params)
data/data/.gitignore CHANGED
@@ -1 +1,2 @@
1
- data.json
1
+ !.gitignore
2
+ *
data/exe/sora CHANGED
@@ -6,9 +6,9 @@ require "optparse"
6
6
 
7
7
  opt = OptionParser.new
8
8
 
9
- params = { port: 8080, document_root: File.expand_path("../../www", __FILE__) }
9
+ params = { port: 8080, document_root: File.expand_path("../www", __dir__) }
10
10
 
11
- opt.on("-p [PORT]", "--port [PORT]") { |v| params[:port] = v.to_i }
11
+ opt.on("-p [PORT]", "--port [PORT]", "Port number, default: 8080") { |v| params[:port] = v.to_i }
12
12
 
13
13
  opt.parse!(ARGV)
14
14
 
data/lib/sora/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sora
4
- VERSION = "24.02.25"
4
+ VERSION = "24.02.26"
5
5
  end
data/lib/sora.rb CHANGED
@@ -19,6 +19,7 @@ module Sora
19
19
  }
20
20
  )
21
21
  srv.mount("/cgi-bin", WEBrick::HTTPServlet::CGIHandler, "#{document_root}/../cgi-bin/main.rb")
22
+ srv.mount("/get", WEBrick::HTTPServlet::CGIHandler, "#{document_root}/../cgi-bin/get.rb")
22
23
  srv.mount("/", WEBrick::HTTPServlet::FileHandler, "#{document_root}/index.html")
23
24
  srv.mount("/style", WEBrick::HTTPServlet::FileHandler, "#{document_root}/style.css")
24
25
  srv.mount("/js", WEBrick::HTTPServlet::FileHandler, "#{document_root}/main.js")
data/www/index.html CHANGED
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html>
2
- <html>
2
+ <html lang="ja">
3
3
 
4
4
  <head>
5
5
  <meta charset="utf-8">
@@ -10,18 +10,43 @@
10
10
 
11
11
  <body>
12
12
  <main id="main">
13
- <h1>sora</h1>
14
- <div class="Grid Text-SendButton">
15
- <input id="content" placeholder="Text..." class="TextBox" />
16
- <input type="button" id="send-button" value="Send" class="Accent Button SendButton" />
13
+ <div id="wrap">
14
+ <h1 id="title-sora">sora</h1>
15
+ <div class="Grid Text-SendButton">
16
+ <input id="content" placeholder="Text..." class="TextBox">
17
+ <input type="button" id="send-button" value="Send" class="Accent Button SendButton">
18
+ <input value="Select a File" type="button" id="select-file-button" class="Button">
19
+ <form id="upload-form" class="Hidden">
20
+ <input placeholder="none" type="file" name="file" id="select-file-button-pre">
21
+ </form>
22
+ </div>
23
+
24
+ <div id="file-list-wrap" class="Hidden">
25
+ <h2 id="file-list-title">Files</h2>
26
+ <div id="file-list">
27
+ <ul id="file-list-ul"></ul>
28
+ </div>
29
+ </div>
30
+
31
+ <div id="data-list-wrap" class="Hidden">
32
+ <h2 id="data-list-title">Texts</h2>
33
+ <div id="data-list">
34
+ </div>
35
+ </div>
17
36
  </div>
18
- <div id="data-list"></div>
19
37
  </main>
20
- <div id="contextmenu">
38
+ <div id="contextmenu" class="contextmenu">
21
39
  <ul>
40
+ <li id="right-click-copy" class="menu-top">Copy</li>
22
41
  <li id="right-click-delete">Delete</li>
23
42
  </ul>
24
43
  </div>
44
+ <div id="contextmenu-file" class="contextmenu">
45
+ <ul>
46
+ <li id="right-click-download-file" class="menu-top Hidden">Download</li>
47
+ <li id="right-click-delete-file">Delete</li>
48
+ </ul>
49
+ </div>
25
50
  <script src="/js"></script>
26
51
  </body>
27
52
 
data/www/main.js CHANGED
@@ -11,14 +11,27 @@ function EncodeHTMLForm(data) {
11
11
 
12
12
  const content = document.getElementById("content");
13
13
  const sendButton = document.getElementById("send-button");
14
+ const selectFileButton = document.getElementById("select-file-button");
15
+ const selectFileButtonPre = document.getElementById("select-file-button-pre");
14
16
  const rightClickDelete = document.getElementById("right-click-delete");
17
+ const rightClickDeleteFile = document.getElementById("right-click-delete-file");
18
+ const rightClickCopy = document.getElementById("right-click-copy");
19
+ const rightClickDownloadFile = document.getElementById("right-click-download-file");
20
+ const contextmenuFile = document.getElementById('contextmenu-file');
21
+ const fileListWrap = document.getElementById('file-list-wrap');
22
+ const dataListWrap = document.getElementById('data-list-wrap');
23
+ const fileListTitle = document.getElementById('file-list-title');
24
+ const dataListTitle = document.getElementById('data-list-title');
25
+
15
26
  let rightClickTargetId = "";
27
+ let fileTarget;
16
28
  let data = [];
17
29
 
18
30
  const refresh = () => {
19
31
  if(data == null) return;
20
32
  const dataList = document.getElementById("data-list");
21
33
  dataList.innerHTML = "";
34
+ dataListWrap.style.display = data.length == 0 ? "none" : "block";
22
35
  for(const text of data){
23
36
  const div = document.createElement("div");
24
37
  div.innerText = text['content'];
@@ -27,15 +40,14 @@ const refresh = () => {
27
40
  dataList.prepend(div);
28
41
 
29
42
  const contextmenu = document.getElementById('contextmenu');
30
- const main = div;
31
- main.addEventListener('contextmenu', (e) => {
43
+ div.addEventListener('contextmenu', (e) => {
32
44
  e.preventDefault();
33
45
  contextmenu.style.left = e.pageX + 'px';
34
46
  contextmenu.style.top = e.pageY + 'px';
35
47
  contextmenu.style.display = 'block';
36
48
  rightClickTargetId = e.target.id
37
49
  });
38
- main.addEventListener('click', () => {
50
+ div.addEventListener('click', () => {
39
51
  contextmenu.style.display = 'none';
40
52
  });
41
53
  }
@@ -45,23 +57,44 @@ const send = (write) => {
45
57
  const xhr = new XMLHttpRequest();
46
58
  xhr.open("POST", "/cgi-bin");
47
59
  write ? xhr.send(EncodeHTMLForm({ "data": content.value })) : xhr.send(EncodeHTMLForm({ "data": "" }));
60
+ if(write && content.value != ""){
61
+ const dataList = document.getElementById("data-list");
62
+ const div = document.createElement("div");
63
+ div.innerText = content.value;
64
+ div.className = "message";
65
+ dataList.prepend(div);
66
+ }
48
67
  xhr.onreadystatechange = () => {
49
68
  let texts = JSON.parse(xhr.responseText || "null");
50
69
  if(texts == null) return;
51
70
  data = texts;
52
71
  refresh();
72
+ sendButton.disabled = false;
73
+ sendButton.classList.remove("disabled")
53
74
  }
54
75
  }
55
76
 
56
77
  const i18n = () => {
57
78
  const lang = window.navigator.language;
58
- if(lang == 'ja'){
79
+ if(lang == 'ja' || lang == 'ja-JP'){
59
80
  content.placeholder = "テキストを入力...";
60
81
  sendButton.value = "送信";
61
- rightClickDelete.innerText = "削除"
82
+ rightClickDelete.innerText = "削除";
83
+ rightClickDeleteFile.innerText = "削除";
84
+ rightClickDownloadFile.innerText = "ダウンロード";
85
+ rightClickCopy.innerText = "コピー";
86
+ selectFileButton.value = "ファイルを選択";
87
+ fileListTitle.innerText = "ファイル一覧";
88
+ dataListTitle.innerText = "テキスト一覧";
62
89
  }
63
90
  }
64
91
 
92
+ rightClickCopy.addEventListener('click', () => {
93
+ const targetDOM = document.getElementById(rightClickTargetId);
94
+ navigator.clipboard.writeText(targetDOM.textContent);
95
+ contextmenu.style.display = 'none';
96
+ });
97
+
65
98
  rightClickDelete.addEventListener('click', () => {
66
99
  const targetDOM = document.getElementById(rightClickTargetId);
67
100
  targetDOM.remove();
@@ -79,6 +112,90 @@ rightClickDelete.addEventListener('click', () => {
79
112
  contextmenu.style.display = 'none';
80
113
  });
81
114
 
115
+ document.body.addEventListener('click', () => {
116
+ contextmenu.style.display = 'none';
117
+ contextmenuFile.style.display = 'none';
118
+ });
119
+
120
+ sendButton.addEventListener("click", () => {
121
+ if(content.value != ""){
122
+ sendButton.disabled = true;
123
+ sendButton.classList.add("disabled")
124
+ send(true)
125
+ }
126
+ });
127
+
128
+ selectFileButton.addEventListener('click', () => {
129
+ selectFileButtonPre.click();
130
+ });
131
+
132
+ selectFileButtonPre.addEventListener('change', (e) => {
133
+ const form = document.getElementById("upload-form");
134
+ const formData = new FormData(form);
135
+
136
+ const xhr = new XMLHttpRequest();
137
+ xhr.open("POST", "/cgi-bin");
138
+ xhr.upload.addEventListener('loadstart', () => {
139
+ console.log("Upload: start")
140
+ });
141
+
142
+ xhr.upload.addEventListener('load', () => {
143
+ // アップロード正常終了
144
+ console.log('Upload: done');
145
+ getFiles();
146
+ });
147
+
148
+ xhr.send(formData);
149
+ });
150
+
151
+ const getFiles = () => {
152
+ const xhr = new XMLHttpRequest();
153
+ xhr.open("POST", "/cgi-bin");
154
+ xhr.send(EncodeHTMLForm({ "files": "" }));
155
+ xhr.onreadystatechange = () => {
156
+ let texts = JSON.parse(xhr.responseText || "null");
157
+ if (texts == null)
158
+ return;
159
+
160
+ const fileListUl = document.getElementById('file-list-ul');
161
+ fileListUl.innerHTML = "";
162
+
163
+ const fileList = document.getElementById('file-list');
164
+ fileList.style.display = texts.length == 0 ? "none" : "block";
165
+ fileListWrap.style.display = texts.length == 0 ? "none" : "block";
166
+
167
+ for(const fileName of texts){
168
+ const div = document.createElement('li');
169
+ div.textContent = fileName;
170
+
171
+ div.addEventListener('contextmenu', (e) => {
172
+ e.preventDefault();
173
+ contextmenuFile.style.left = e.pageX + 'px';
174
+ contextmenuFile.style.top = e.pageY + 'px';
175
+ contextmenuFile.style.display = 'block';
176
+ fileTarget = e.target;
177
+ });
178
+ div.addEventListener('click', () => {
179
+ contextmenuFile.style.display = 'none';
180
+ location.href = `/get?filename=${fileName}`
181
+ });
182
+
183
+ fileListUl.append(div);
184
+ }
185
+ };
186
+ };
187
+
188
+ rightClickDeleteFile.addEventListener("click", () => {
189
+ const fileName = fileTarget.textContent;
190
+ fileTarget.remove();
191
+ const xhr = new XMLHttpRequest();
192
+ xhr.open("POST", "/cgi-bin");
193
+ xhr.send(EncodeHTMLForm({ "removefile": fileName }));
194
+ xhr.onreadystatechange = () => {
195
+ getFiles();
196
+ };
197
+ })
198
+
82
199
  i18n();
83
200
  send(false);
84
- sendButton.addEventListener("click", () => { send(true) });
201
+ getFiles();
data/www/style.css CHANGED
@@ -1,27 +1,42 @@
1
- body{
1
+ html {
2
+ height: 100%;
3
+ }
4
+
5
+ body {
2
6
  background-color: #F3F3F3;
3
7
  font-family: Segoe UI, Meiryo UI;
8
+ min-height: 100%;
9
+ margin: 0;
4
10
  }
5
11
 
6
- main{
12
+ main {
7
13
  max-width: 980px;
8
14
  margin: 0 auto;
9
15
  }
10
16
 
11
- .Grid{
17
+ #wrap {
18
+ margin: 0 16px;
19
+ padding-top: 8px;
20
+ }
21
+
22
+ .Grid {
12
23
  display: grid;
13
24
  }
14
25
 
15
- .Text-SendButton{
16
- grid-template-columns: 1fr auto;
26
+ .Text-SendButton {
27
+ grid-template-columns: 1fr auto auto;
17
28
  margin-bottom: 8px;
18
29
  }
19
30
 
20
- .Text-SendButton .TextBox{
31
+ .Text-SendButton .TextBox {
21
32
  margin-right: 8px;
22
33
  }
23
34
 
24
- .message{
35
+ #send-button{
36
+ margin-right: 8px;
37
+ }
38
+
39
+ .message {
25
40
  margin-bottom: 8px;
26
41
  padding: 16px;
27
42
  border: solid 1px #E5E5E5;
@@ -30,8 +45,49 @@ main{
30
45
  font-size: 14px;
31
46
  }
32
47
 
48
+ #contextmenu ul li {
49
+ margin-top: 4px;
50
+ }
51
+
52
+ #contextmenu ul li.menu-top {
53
+ margin-top: 0;
54
+ }
55
+
56
+ /* ファイルリスト */
57
+ #file-list{
58
+ background-color: #F9F9F9;
59
+ padding: 8px;
60
+ margin-bottom: 8px;
61
+ border: solid 1px #E3E3E3;
62
+ border-radius: 8px;
63
+ display: none;
64
+ }
65
+
66
+ #file-list-ul{
67
+ padding: 0;
68
+ margin: 0;
69
+ list-style-type: none;
70
+ user-select: none;
71
+ }
72
+
73
+ #file-list-ul li{
74
+ border-radius: 4px;
75
+ padding: 4.75px 12px 4.75px 12px;
76
+ cursor: pointer;
77
+ margin-top: 4px;
78
+ font-size: 14px;
79
+ }
80
+
81
+ #file-list-ul li:nth-child(1){
82
+ margin-top: 0;
83
+ }
84
+
85
+ #file-list-ul li:hover{
86
+ background-color: #F0F0F0;
87
+ }
88
+
33
89
  /* 右クリックメニュー */
34
- #contextmenu {
90
+ .contextmenu {
35
91
  display: none;
36
92
  position: fixed;
37
93
  left: 0;
@@ -43,12 +99,12 @@ main{
43
99
  width: 192px;
44
100
  }
45
101
 
46
- #contextmenu ul{
102
+ .contextmenu ul {
47
103
  padding-left: 0;
48
104
  margin: 0;
49
105
  }
50
106
 
51
- #contextmenu li {
107
+ .contextmenu li {
52
108
  cursor: pointer;
53
109
  margin: 0;
54
110
  padding: 4.75px 0 4.75px 16px;
@@ -58,12 +114,16 @@ main{
58
114
  user-select: none;
59
115
  }
60
116
 
61
- #contextmenu li:hover {
117
+ .contextmenu li:hover {
62
118
  background-color: #F0F0F0;
63
119
  }
64
120
 
65
121
  /* 共通コンポーネント */
66
- .TextBox{
122
+ .Hidden {
123
+ display: none;
124
+ }
125
+
126
+ .TextBox {
67
127
  height: 27px;
68
128
  border: solid 1px #E5E5E5;
69
129
  border-bottom-color: #868686;
@@ -73,11 +133,11 @@ main{
73
133
  background-color: #FBFBFB;
74
134
  }
75
135
 
76
- .TextBox:hover{
136
+ .TextBox:hover {
77
137
  background-color: #F6F6F6;
78
138
  }
79
139
 
80
- .TextBox:focus{
140
+ .TextBox:focus {
81
141
  background-color: white;
82
142
  height: 27px;
83
143
  border-bottom-color: #0067C0;
@@ -86,7 +146,8 @@ main{
86
146
  padding-bottom: 1px;
87
147
  }
88
148
 
89
- .Button{
149
+ .Button {
150
+ cursor: pointer;
90
151
  height: 32px;
91
152
  border: solid 1px #E5E5E5;
92
153
  border-bottom-color: #CCCCCC;
@@ -95,32 +156,40 @@ main{
95
156
  padding: 1px 12px;
96
157
  }
97
158
 
98
- .Button:hover{
159
+ .Button:hover {
99
160
  background-color: #F6F6F6;
100
161
  }
101
162
 
102
- .Button:active{
163
+ .Button:active {
103
164
  background-color: #F5F5F5;
104
165
  color: #5D5D5D;
105
166
  border-color: #E5E5E5;
106
167
  }
107
168
 
108
- .Button.Accent{
169
+ .Button.Accent {
109
170
  background-color: #0067C0;
110
171
  border: solid 1px #1473C5;
111
172
  border-bottom-color: #003E73;
112
173
  color: white;
113
174
  }
114
175
 
115
- .Button.Accent:hover{
176
+ .Button.Accent:hover {
116
177
  background-color: #1975C5;
117
178
  border: solid 1px #2B80CA;
118
179
  border-bottom-color: #0F4676;
119
180
  }
120
181
 
121
- .Button.Accent:active{
182
+ .Button.Accent:active {
122
183
  background-color: #3183CA;
123
184
  border: solid 1px #3183CA;
124
185
  border-bottom-color: #3183CA;
125
186
  color: #C2DAEF;
126
187
  }
188
+
189
+ .Button.disabled, .Button.disabled:hover {
190
+ cursor: not-allowed;
191
+ background-color: #F5F5F5 !important;
192
+ color: #9D9D9D;
193
+ border: solid 1px #E5E5E5 !important;
194
+ border-bottom-color: #E5E5E5 !important;
195
+ }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sora
3
3
  version: !ruby/object:Gem::Version
4
- version: 24.02.25
4
+ version: 24.02.26
5
5
  platform: ruby
6
6
  authors:
7
7
  - MURATA Mitsuharu
@@ -9,7 +9,21 @@ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
11
  date: 2024-02-25 00:00:00.000000000 Z
12
- dependencies: []
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: webrick
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.8'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.8'
13
27
  description: sora (Server Of Ruby for Access) can send and receive files, and transmit
14
28
  text strings to communicate information.
15
29
  email:
@@ -25,6 +39,7 @@ files:
25
39
  - LICENSE.txt
26
40
  - README.md
27
41
  - Rakefile
42
+ - cgi-bin/get.rb
28
43
  - cgi-bin/main.rb
29
44
  - data/.gitignore
30
45
  - exe/sora