inat-get 0.8.0.11 → 0.8.0.12

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: 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