inat-get 0.8.0.11 → 0.8.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd46b6875ef0b9feaa0a8632c86ad0a72d234958452bd04c7e879f433324c53f
4
- data.tar.gz: 91b31159aba05e86a5f5fd154bb197267a55b3ccbf6b14781fb399bd249a27ed
3
+ metadata.gz: 8b6b2164211848152ba97642e69979744046065cc6916b7f64d5f0b6127137e2
4
+ data.tar.gz: c3578401e4ac8127126da08d124c27834d4ce0ed3c083fe0de61aefcc9b72618
5
5
  SHA512:
6
- metadata.gz: 1cf679179ea4e548ea3ced47bb7e6cd9039d6a5c79404f5a944b2fb098bb4520b23d77e3116628e59acdd25bd359877ff60a9c81fcda84d4a0a53faba0354c49
7
- data.tar.gz: 0606f69e8a4fac67df1382fd67f3bda21bd678e5f8655b58deb4355018be4ba29f3dfb0bce7411b51fc05e8c5be34227c361cf2fe2810afe6b059b7606ccad37
6
+ metadata.gz: 9cfa13aeecf17554be9d62b41c4ef1cac2018e916e49aa9cc9698260c36598e1f90acfeb2cd89f0320027446fb58f099a4d3f65f8a48f7c358b2db16f8630a05
7
+ data.tar.gz: ddee5675c3b2b832a9ecfa35e37a0059947aea87e98cce5f67ab7e7179e41750f08fe15c3937756268e2b8ac3c9ba6c137bf2996f5077847ca3a98997c3352c0
data/lib/inat/app/info.rb CHANGED
@@ -5,7 +5,7 @@ module AppInfo
5
5
  AUTHOR = 'Ivan Shikhalev'
6
6
  EMAIL = 'shkikhalev@gmail.com'
7
7
 
8
- VERSION = '0.8.0.11'
8
+ VERSION = '0.8.0.12'
9
9
  HOMEPAGE = 'https://github.com/shikhalev/inat-get'
10
10
  SOURCE_URL = 'https://github.com/shikhalev/inat-get'
11
11
 
@@ -2,11 +2,13 @@
2
2
 
3
3
  require_relative 'dsl'
4
4
  require_relative '../../report/table'
5
+ require_relative '../../report/report_dsl'
5
6
 
6
7
  class Task::Context
7
8
 
8
9
  include Task::DSL
9
10
  include TableDSL
11
+ include ReportDSL
10
12
 
11
13
  attr_accessor :name
12
14
 
@@ -6,7 +6,7 @@ require_relative 'list'
6
6
  class DataSet
7
7
 
8
8
  attr_reader :time
9
- attr_reader :object
9
+ attr_accessor :object # TODO: переделать select так, чтобы не было необходимости во внешнем присваивании
10
10
  attr_reader :observations
11
11
 
12
12
  def initialize object, observations, time: Time::new
@@ -10,6 +10,7 @@ module Listers
10
10
  YEAR = lambda { |o| Year[o.observed_on] }
11
11
  MONTH = lambda { |o| Month[o.observed_on] }
12
12
  DAY = lambda { |o| Day[o.observed_on] }
13
+ WINTER = lambda { |o| Winter[o.observed_on] }
13
14
  USER = lambda { |o| o.user }
14
15
 
15
16
  end
@@ -2,136 +2,161 @@
2
2
 
3
3
  require 'date'
4
4
 
5
- class Year
5
+ class Calendarian
6
6
 
7
7
  class << self
8
8
 
9
9
  private :new
10
10
 
11
- def [] source
12
- return nil if source == nil
13
- return source if Year === source
14
- year = case source
15
- when Date, Time
16
- source.year
11
+ def [] src
12
+ return nil if src == nil
13
+ return src if self === src
14
+ value = case src
15
+ when Date
16
+ self.date_to_value src
17
+ when Time
18
+ self.date_to_value src.to_date
17
19
  when String
18
- date = Date::parse source
19
- date.year
20
+ date = Date::parse src
21
+ self.date_to_value date
20
22
  when Integer
21
- source
23
+ src
22
24
  else
23
- raise TypeError, "Invalid year key: #{ source.inspect }!", caller
25
+ raise TypeError, "Invalid date: #{ src.inspect }", caller
24
26
  end
25
- @years ||= {}
26
- @years[year] ||= new year
27
- @years[year]
27
+ return nil if value == nil
28
+ @values ||= {}
29
+ @values[value] ||= new value
30
+ @values[value]
28
31
  end
29
32
 
30
33
  end
31
34
 
32
- attr_reader :year
35
+ attr_reader :value
33
36
 
34
- def initialize year
35
- @year = year
37
+ def initialize value
38
+ @value = value
36
39
  end
37
40
 
38
41
  include Comparable
39
42
 
40
43
  def <=> other
41
- return nil unless Year === other
42
- @year <=> other.year
44
+ return nil unless self.class === other
45
+ @value <=> other.value
43
46
  end
44
47
 
48
+ end
49
+
50
+ class Year < Calendarian
51
+
52
+ class << self
53
+
54
+ protected def date_to_value date
55
+ date.year
56
+ end
57
+
58
+ end
59
+
60
+ alias :year :value
61
+
45
62
  def - num
46
- self.class[@year - num]
63
+ self.class[@value - num]
47
64
  end
48
65
 
49
66
  def to_s
50
- "<i class=\"glyphicon glyphicon-calendar\"></i>  #{ @year }"
67
+ "<i class=\"glyphicon glyphicon-calendar\"></i>  #{ @value } год"
68
+ end
69
+
70
+ def query_params
71
+ "year=#{ @value }"
51
72
  end
52
73
 
53
74
  end
54
75
 
55
- class Month
76
+ class Month < Calendarian
56
77
 
57
78
  class << self
58
79
 
59
- private :new
60
-
61
- def [] source
62
- return nil if source == nil
63
- return source if Month === source
64
- month = case source
65
- when Date, Time
66
- source.month
67
- when String
68
- date = Date::parse source
69
- date.month
70
- when Integer
71
- source
72
- else
73
- raise TypeError, "Invalid month key: #{ source.inspect }!", caller
74
- end
75
- @months ||= {}
76
- @months[month] ||= new month
77
- @months[month]
80
+ protected def date_to_value date
81
+ date.month
78
82
  end
79
83
 
80
84
  end
81
85
 
82
- attr_reader :month
86
+ alias :month :value
87
+
88
+ NAMES = {
89
+ 1 => 'Январь',
90
+ 2 => 'Февраль',
91
+ 3 => 'Март',
92
+ 4 => 'Апрель',
93
+ 5 => 'Май',
94
+ 6 => 'Июнь',
95
+ 7 => 'Июль',
96
+ 8 => 'Август',
97
+ 9 => 'Сентябрь',
98
+ 10 => 'Октябрь',
99
+ 11 => 'Ноябрь',
100
+ 12 => 'Декабрь'
101
+ }
83
102
 
84
- def initialize month
85
- @month = month
103
+ def to_s
104
+ "<i class=\"glyphicon glyphicon-calendar\"></i>  #{ NAMES[@value] }"
86
105
  end
87
106
 
88
- include Comparable
89
-
90
- def <=> other
91
- return nil unless Month === other
92
- @month <=> other.month
107
+ def query_params
108
+ "month=#{ @value }"
93
109
  end
94
110
 
95
111
  end
96
112
 
97
- class Day
113
+ class Day < Calendarian
98
114
 
99
115
  class << self
100
116
 
101
- attr_reader :day
117
+ protected def date_to_value date
118
+ date.day
119
+ end
102
120
 
103
- def [] source
104
- return nil if source == nil
105
- return source if Day === source
106
- day = case source
107
- when Date, Time
108
- source.day
109
- when String
110
- date = Date::parse source
111
- date.day
112
- when Integer
113
- source
121
+ end
122
+
123
+ alias :day :value
124
+
125
+ def to_s
126
+ "<i class=\"glyphicon glyphicon-calendar\"></i>  #{ @value }"
127
+ end
128
+
129
+ def query_params
130
+ "day=#{ @value }"
131
+ end
132
+
133
+ end
134
+
135
+ class Winter < Calendarian
136
+
137
+ class << self
138
+
139
+ protected def date_to_value date
140
+ month = date.month
141
+ if month <= 4
142
+ date.year
143
+ elsif month >= 10
144
+ date.year + 1
114
145
  else
115
- raise TypeError, "Invalid day key: #{ source.inspect }!", caller
146
+ nil
116
147
  end
117
- @days ||= {}
118
- @days[day] ||= new day
119
- @days[day]
120
148
  end
121
149
 
122
150
  end
123
151
 
124
- attr_reader :day
152
+ alias :winter :value
125
153
 
126
- def initialize day
127
- @day = day
154
+ def to_s
155
+ "<i class=\"glyphicon glyphicon-calendar\"></i>  Зима #{ @value - 1 }–#{ @value }"
128
156
  end
129
157
 
130
- include Comparable
131
-
132
- def <=> other
133
- return nil unless Day === other
134
- @day <=> other.day
158
+ def query_params
159
+ "d1=#{ @value - 1 }-10-01&d2=#{ @value }-04-30"
135
160
  end
136
161
 
137
162
  end
@@ -0,0 +1,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'table'
4
+
5
+ module ReportDSL
6
+
7
+ include TableDSL
8
+
9
+ def class_title object, is_observer: true
10
+ case object
11
+ when Taxon
12
+ 'Таксон'
13
+ when Place
14
+ 'Территория'
15
+ when Project
16
+ 'Проект'
17
+ when User
18
+ if is_observer
19
+ 'Наблюдатель'
20
+ else
21
+ 'Пользователь'
22
+ end
23
+ when Calendarian
24
+ 'Период'
25
+ else
26
+ 'Объект'
27
+ end
28
+ end
29
+
30
+ def species_table list, observers: false, details: true
31
+ raise ArgumentError, 'Unconsistent flags!' if observers && !details
32
+ object_title = class_title list.first.object
33
+ species = table do
34
+ column '#', width: 3, align: :right, data: :line_no
35
+ column object_title, data: :object
36
+ if details
37
+ column 'Наблюдения', data: :observations
38
+ else
39
+ column 'Наблюдения', data: :observations, width: 6, align: :right
40
+ end
41
+ end
42
+ users = nil
43
+ user_rows = []
44
+ if observers
45
+ @@prefix ||= 0
46
+ @@prefix += 1
47
+ users = table do
48
+ column '#', width: 3, align: :right, data: :line_no
49
+ column 'Наблюдатель', data: :user
50
+ column 'Виды', width: 6, align: :right, data: :species
51
+ column 'Наблюдения', width: 6, align: :right, data: :observations
52
+ end
53
+ by_user = list.to_dataset.to_list Listers::USER
54
+ by_user.each do |ds|
55
+ ls = ds.to_list Listers::SPECIES
56
+ user = ds.object
57
+ user_rows << { user: user, anchor: "#{ @@prefix }-user-#{ user.id }", species: ls.count, observations: ds.count }
58
+ end
59
+ user_rows.sort_by! { |row| -row[:species] }
60
+ users << user_rows
61
+ end
62
+ species_rows = []
63
+ list.each do |ds|
64
+ observations = if details
65
+ if observers
66
+ ds.observations.map { |o| "#{ o }<sup><a href=\"\##{ @@prefix }-user-#{ o.user.id }\">#{ user_rows.index { |row| row[:user] == o.user } }</a></sup>" }
67
+ else
68
+ ds.observations.map(&:to_s)
69
+ end
70
+ else
71
+ [ ds.count.to_s ]
72
+ end
73
+ species_rows << { object: ds.object, observations: observations.join(', ') }
74
+ end
75
+ species << species_rows
76
+ if observers
77
+ [ species, users ]
78
+ else
79
+ species
80
+ end
81
+ end
82
+
83
+ LAST_STYLE = 'font-size:110%;'
84
+ SUMMARY_STYLE = 'font-weight:bold;'
85
+
86
+ def history_table list, news: true, summary: true, base_link: nil, last: nil, extras: false, last_style: LAST_STYLE, summary_style: SUMMARY_STYLE
87
+ object_title = class_title list.first.object, is_observer: false
88
+ history = table do
89
+ column '#', width: 3, align: :right, data: :line_no
90
+ column object_title, data: :object
91
+ column 'Наблюдения', width: 6, align: :right, data: :observations
92
+ column 'Виды', width: 6, align: :right, data: :species
93
+ if news
94
+ column 'Новые', width: 6, align: :right, data: :news
95
+ end
96
+ end
97
+ history_rows = []
98
+ last_object = nil
99
+ last_ds = nil
100
+ last_ls = nil
101
+ delta = nil
102
+ if news || summary
103
+ base_ls = List::zero Listers::SPECIES
104
+ end
105
+ list.each do |ds|
106
+ last_object = ds.object
107
+ last_ds = ds
108
+ last_ls = ds.to_list Listers::SPECIES
109
+ row = { observations: last_ds.count, species: last_ls.count }
110
+ if base_link && last_object.respond_to?(:query_params)
111
+ link = base_link + '&' + last_object.query_params
112
+ row[:object] = "<a href=\"#{ link }\">#{ last_object }</a>"
113
+ else
114
+ row[:object] = last_object
115
+ end
116
+ if news || summary
117
+ delta = last_ls - base_ls
118
+ base_ls += last_ls
119
+ row[:news] = delta.count
120
+ end
121
+ history_rows << row
122
+ end
123
+ if last && last != last_object
124
+ row = { object: last, observations: 0, species: 0 }
125
+ if news
126
+ row[:news] = 0
127
+ delta = List::zero Listers::SPECIES
128
+ last_object = last
129
+ last_ds = DataSet::zero
130
+ last_ls = List::zero Listers::SPECIES
131
+ end
132
+ history_rows << row
133
+ end
134
+ history_rows.last[:style] = last_style if last_style
135
+ if summary
136
+ # Почему base_ls, а не list?
137
+ # 1. list в некоторых случаях может быть не List, а просто массив DataSet с заполненным object.
138
+ # 2. Даже в базовом случае list сформирован не по видам.
139
+ row = { line_no: '', observations: base_ls.observation_count, species: base_ls.count }
140
+ row[:style] = summary_style if summary_style
141
+ history_rows << row
142
+ end
143
+ history << history_rows
144
+ if extras
145
+ [ history, last_ds, delta ]
146
+ else
147
+ history
148
+ end
149
+ end
150
+
151
+ # Вариант history_table с другими предустановками.
152
+ def summary_table list, extras: false, summary_style: SUMMARY_STYLE
153
+ history_table list, news: false, summary: true, base_link: nil, last: nil, extras: extras, last_style: nil, summary_style: summary_style
154
+ end
155
+
156
+ # TODO: разобраться с разными count в разных контекстах.
157
+ def rating_table list, limit: 1, count: 3, details: true, key: :species, summary: false
158
+ object_title = class_title list.first.object
159
+ rating = table do
160
+ column '#', width: 3, align: :right, data: :line_no
161
+ column object_title, data: :object
162
+ column 'Виды', width: 6, align: :right, data: :species
163
+ if details
164
+ column 'Наблюдения', data: :observations
165
+ else
166
+ column 'Наблюдения', width: 6, align: :right, data: :observations
167
+ end
168
+ end
169
+ rating_rows = []
170
+ list.each do |ds|
171
+ ls = ds.to_list Listers::SPECIES
172
+ row = { object: ds.object, species: ls.count, count: ds.count }
173
+ if details
174
+ row[:observations] = ds.observations.map(&:to_s).join(', ')
175
+ else
176
+ row[:observations] = ds.count
177
+ end
178
+ rating_rows << row
179
+ end
180
+ size = rating_rows.size
181
+ key = :count if key == :observations
182
+ if limit
183
+ rating_rows.filter! { |row| row[key] >= limit }
184
+ end
185
+ rating_rows.sort_by! { |row| -row[key] }
186
+ if count
187
+ rating_rows = rating_rows.take(count)
188
+ end
189
+ if summary
190
+ full_ls = List === list ? list : list.reduce(DataSet::zero, :|).to_list
191
+ rating_rows << { line_no: '', species: full_ls.count, count: full_ls.observation_count }
192
+ end
193
+ rating << rating_rows
194
+ if count
195
+ [ rating, size ]
196
+ else
197
+ rating
198
+ end
199
+ end
200
+
201
+ module_function :species_table, :history_table, :summary_table, :rating_table
202
+
203
+ end
@@ -41,6 +41,10 @@ class Table
41
41
  @rows
42
42
  end
43
43
 
44
+ def empty?
45
+ @rows.empty?
46
+ end
47
+
44
48
  def << data
45
49
  if Array === data
46
50
  rows(*data)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inat-get
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0.11
4
+ version: 0.8.0.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Shikhalev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-11-22 00:00:00.000000000 Z
11
+ date: 2023-11-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sqlite3
@@ -107,6 +107,7 @@ files:
107
107
  - lib/inat/data/types/extras.rb
108
108
  - lib/inat/data/types/location.rb
109
109
  - lib/inat/data/types/std.rb
110
+ - lib/inat/report/report_dsl.rb
110
111
  - lib/inat/report/table.rb
111
112
  - lib/inat/utils/deep.rb
112
113
  homepage: https://github.com/shikhalev/inat-get